接下来的方法是我在非常丑陋的情况下想出的一种方法,在这种情况下,我需要做出“最大努力”。 ”,以将无限制的未知类型的对象转换为无限制的未知类型。该代码只是困扰我。似乎应该有一种更优雅的方法来执行此操作,但是我想让“尽力而为”这一部分正确。
这些方法遵循“ Try”约定。它接受一个对象“值”和类型为T的out参数“结果”。它尝试将值类型T转换为结果。如果成功,则返回true。如果不能,则设置result = default(T)并返回false。
感觉我在方法上遇到了很多麻烦。我乐于接受一些简化建议。此处包含的注释大多不是原始注释...只是为了解释为什么有些事情是照原样进行的。
#1 楼
对于转换,我想到的是一个简单得多的方法:public static bool TryCast<T>(this object obj, out T result)
{
if (obj is T)
{
result = (T)obj;
return true;
}
result = default(T);
return false;
}
您无需手动检测
nullable
类型,因为is
运算符已对其进行检查:5 is int?
返回true ,因此以下代码将5
写入控制台。int value = 5;
int? result;
if (value.TryCast(out result))
Console.WriteLine(result);
以下内容不写任何内容,因为
TryCast
返回false。string value = "5";
int? test;
if (value.TryCast(out test))
Console.WriteLine(test);
最后,下面的代码应该写成两行,分别是“测试1”和“测试2”。
var list = new List<string>();
list.Add("test 1");
list.Add("test 2");
IEnumerable<string> enumerable;
if (list.TryCast(out enumerable))
foreach (var item in enumerable)
Console.WriteLine(item);
我真的反对这种方法:
if (underlyingType == typeof(Guid))
{
if (value is string)
{
value = new Guid(value as string);
}
else if (value is byte[])
{
value = new Guid(value as byte[]);
}
//...
如果您想要这种自定义转换功能,我的建议是将转换器保存在静态的,线程安全的
Converter
集合中。一个示例转换器类可以是这样的:public abstract class Converter
{
private readonly Type from; // Type of the instance to convert.
private readonly Type to; // Type that the instance will be converted to.
// Internal, because we'll provide the only implementation...
// ...that's also why we don't check if the arguments are null.
internal Converter(Type from, Type to)
{
this.from = from;
this.to = to;
}
public Type From { get { return this.from; } }
public Type To { get { return this.to; } }
public abstract object Convert(object obj);
}
实现是:
// Sealed, because this is meant to be the only implementation.
public sealed class Converter<TFrom, TTo> : Converter
{
Func<TFrom, TTo> converter; // Converter is strongly typed.
public Converter(Func<TFrom, TTo> converter)
: base(typeof(TFrom), typeof(TTo)) // Can't send null types to the base.
{
if (converter == null)
throw new ArgumentNullException("converter", "Converter must not be null.");
this.converter = converter;
}
public override object Convert(object obj)
{
if (!(obj is TFrom))
{
var msg = string.Format("Object is not of the type {0}.", this.From.FullName);
throw new ArgumentException(msg, "obj");
}
// Can throw exception, it's ok.
return this.converter.Invoke((TFrom)obj);
}
}
进行初始化它:
var int32ToString = new Converter<int, string>(i => i.ToString());
var stringToInt32 = new Converter<string, int>(s => int.Parse(s));
// Converters should be a thread-safe collection of our abstract `Converter` type.
Converters.Add(int32ToString);
Converters.Add(stringToInt32);
由于支持自定义转换器,最终的
TryCast
方法变为:public static bool TryCast<T>(this object obj, out T result)
{
if (obj is T)
{
result = (T)obj;
return true;
}
// If it's null, we can't get the type.
if (obj != null)
{
var converter = Converters.FirstOrDefault(c =>
c.From == obj.GetType() && c.To == typeof(T));
// Use the converter if there is one.
if (converter != null)
try
{
result = (T)converter.Convert(obj);
return true;
}
catch (Exception)
{
// Ignore - "Try*" methods don't throw exceptions.
}
}
result = default(T);
return false;
}
评论
\ $ \ begingroup \ $
您可以使用“ as”关键字将TryCast
\ $ \ endgroup \ $
–user2023861
16年1月25日在18:16
\ $ \ begingroup \ $
@ user2023861如果我没记错的话,除非在T:class上,否则不能使用。
\ $ \ endgroup \ $
–ŞafakGür
16 Jan 28'在9:46
\ $ \ begingroup \ $
@ user2023861另外,结果等于默认值不是可靠的指示,表示转换失败。
\ $ \ endgroup \ $
– Nikita B
18年6月15日在9:42
#2 楼
System.ComponentModel.TypeDescriptor
命名空间(System.dll)中有内置的转换器。您不必编写自己的数组,也可以访问一堆预先存在的转换器。这是修改后的版本,可以解决此问题:
public static bool TryCast<T>(object obj, out T result)
{
result = default(T);
if (obj is T)
{
result = (T)obj;
return true;
}
// If it's null, we can't get the type.
if (obj != null)
{
var converter = TypeDescriptor.GetConverter(typeof (T));
if(converter.CanConvertFrom(obj.GetType()))
result = (T) converter.ConvertFrom(obj);
else
return false;
return true;
}
//Be permissive if the object was null and the target is a ref-type
return !typeof(T).IsValueType;
}
(当然,允许性取决于您计划使用它的方式,我宁愿尝试一下转换,因为我会对其进行检查)
评论
\ $ \ begingroup \ $
欢迎使用代码审查!这看起来是一个有价值的答案!即使您的建议只是“ System.ComponentModel.TypeDescriptor命名空间(System.dll)中有内置的转换器,您也不必编写自己的数组,也可以访问许多预先存在的转换器。”那将是一个有价值的评论。即使是一小部分反馈也值得发表。
\ $ \ endgroup \ $
–SuperBiasedMan
16年1月25日在17:10
\ $ \ begingroup \ $
我相信您的TryCast方法有时会引发异常。我认为将不会抛出异常,而不是从(Int32)1或(Int32)999或(String)“ 1”到Byte,而是为(String)“ 999”。我不知道这是否对任何人都有问题,但是也许可以避免这种情况?
\ $ \ endgroup \ $
–杰米·特尔斯(Jamie Twells)
17年6月12日在10:45
\ $ \ begingroup \ $
@captainjamie您的意思是类似TryCast
\ $ \ endgroup \ $
– GettnDer
17年6月13日在17:53
\ $ \ begingroup \ $
@GettnDer TryCast
\ $ \ endgroup \ $
–杰米·特尔斯(Jamie Twells)
17年6月14日在10:39
#3 楼
我使用GettnDer的答案并对其进行了更新,以利用c#8语言功能,从而消除了对铸造的某些需求,并展示了如何使“ 123”能够被解析并测试类型,IP地址是否相同等等。事物支持解析。我有一个表示要实现IParse的接口,所以我测试是否可以想到很多我认为tryCast方法可以解决问题,并且有效使用新功能的大多数注释可能是:
if(T is IParse parse)
{
return parse.TryParse<T>(obj.ToString(),out result );
}
将在所有支持c#8的框架中工作,因此.net core 3.1 Net 5.0和.net standard 2.1
评论
如果您认为代码中的某些内容需要解释,为什么不在原始代码中包含这些注释?如果您希望我们对您的代码感到困惑,那么为什么您认为您的同事不会呢?仅作比较,请查看我编写的常规转换扩展。我们已经对此进行了测试,并且看起来效果很好:goo.gl/CoE8Xx没有Try版本,因为我们具有通用的Attempt机制:goo.gl/DfWwYe