这不是紧急的,更多的是琐事或挑战。该解决方案虽然可以正常工作,但我怀疑它会更好。

接下来的方法是我在非常丑陋的情况下想出的一种方法,在这种情况下,我需要做出“最大努力”。 ”,以将无限制的未知类型的对象转换为无限制的未知类型。该代码只是困扰我。似乎应该有一种更优雅的方法来执行此操作,但是我想让“尽力而为”这一部分正确。

这些方法遵循“ Try”约定。它接受一个对象“值”和类型为T的out参数“结果”。它尝试将值类型T转换为结果。如果成功,则返回true。如果不能,则设置result = default(T)并返回false。

感觉我在方法上遇到了很多麻烦。我乐于接受一些简化建议。此处包含的注释大多不是原始注释...只是为了解释为什么有些事情是照原样进行的。

评论

如果您认为代码中的某些内容需要解释,为什么不在原始代码中包含这些注释?如果您希望我们对您的代码感到困惑,那么为什么您认为您的同事不会呢?

仅作比较,请查看我编写的常规转换扩展。我们已经对此进行了测试,并且看起来效果很好:goo.gl/CoE8Xx没有Try版本,因为我们具有通用的Attempt机制:goo.gl/DfWwYe

#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 实现简化为仅两行。 bool TryCast (此对象obj,输出T结果){result = obj as T;返回结果!= default(T); }
\ $ \ 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 (“ 999”,b)一样吗?也许可以改进get转换器的用法,随时更新我的​​问题或添加正确的try-catch也值得注意的是,您可以通过两种方式检查转换器:TypeConverter targetConverter = TypeDescriptor.GetConverter(typeof(T)); TypeConverter sourceConverter = TypeDescriptor.GetConverter(obj.GetType());
\ $ \ endgroup \ $
– GettnDer
17年6月13日在17:53



\ $ \ begingroup \ $
@GettnDer TryCast (“ 999”,出b)正是我的意思。正确的try-catch将是catch(Exception),因为转换器会抛出基类。这是一个很难解决的问题,似乎没有很好的解决方案。
\ $ \ 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