.NET中的智能类型转换

时间:2013-03-02 11:00:03

标签: c# .net algorithm types converter

我一直在尝试将字符串转换为.NET中的值类型,其结果值类型未知。我在代码中遇到的问题是我需要一个接受字符串的方法,并使用“最适合”的方法来填充结果值类型。如果机制找不到合适的匹配,则返回该字符串。

这就是我的想法:

public static dynamic ConvertToType(string value)
{
    Type[] types = new Type[]
    {
        typeof(System.SByte),
        typeof(System.Byte), 
        typeof(System.Int16), 
        typeof(System.UInt16), 
        typeof(System.Int32), 
        typeof(System.UInt32), 
        typeof(System.Int64), 
        typeof(System.UInt64), 
        typeof(System.Single), 
        typeof(System.Double), 
        typeof(System.Decimal),
        typeof(System.DateTime),
        typeof(System.Guid)
    };
    foreach (Type type in types)
    {
         try
         {
               return Convert.ChangeType(value, type);
         }
         catch (Exception)
         {
             continue;
         }
    }
    return value;
}

我觉得这种做法可能不是最佳做法,因为它只能与预定义的类型相匹配。

通常我发现.NET以比我的实现更好的方式容纳这个功能,所以我的问题是:有没有更好的方法解决这个问题和/或这个功能在.NET中实现得更好?

编辑:请注意,数组中类型的排序是为了使“最佳拟合”尽可能准确地出现在给定类型中。

编辑:根据miniBill的请求,我可以使用该方法(简单示例!):

JsonDictionary["myKey"] = ConvertToType("255"); // 255 is a stringified json value, which should be assigned to myKey as a byte.

4 个答案:

答案 0 :(得分:2)

如果value不是SByte,您的方法会导致一系列例外,因此您的方法并不理想。

看到所有这些类型共享一个公共方法.TryParse(string, out T),我们可以使用反射提取方法并为每种类型调用它。我在string上将该方法作为扩展方法,并将Type[]数组分解为其自己的延迟加载属性,以便更快地使用。

public static class StringExtensions
{
    public static dynamic ConvertToType(this string value)
    {
        foreach (Type type in ConvertibleTypes)
        {
            var obj = Activator.CreateInstance(type);
            var methodParameterTypes = new Type[] { typeof(string), type.MakeByRefType() };
            var method = type.GetMethod("TryParse", methodParameterTypes);
            var methodParameters = new object[] { value, obj };

            bool success = (bool)method.Invoke(null, methodParameters);

            if (success)
            {
                return methodParameters[1];
            }
        }
        return value;
    }

    private static Type[] _convertibleTypes = null;

    private static Type[] ConvertibleTypes
    {
        get
        {
            if (_convertibleTypes == null)
            {
                _convertibleTypes = new Type[]
                {
                    typeof(System.SByte),
                    typeof(System.Byte), 
                    typeof(System.Int16), 
                    typeof(System.UInt16), 
                    typeof(System.Int32), 
                    typeof(System.UInt32), 
                    typeof(System.Int64), 
                    typeof(System.UInt64), 
                    typeof(System.Single), 
                    typeof(System.Double), 
                    typeof(System.Decimal),
                    typeof(System.DateTime),
                    typeof(System.Guid)
                };
            }
            return _convertibleTypes;
        }
    }
}

用法:

string value = "2391203921";
dynamic converted = value.ConvertToType();

答案 1 :(得分:0)

你的方法可行,但正如你所说,它并不那么优雅。

我认为您有几种方法可以改进此代码:

  1. 将数组移出函数,如psubsee2003所说
  2. 使用TryParse方法进行更便宜的测试(不涉及捕捉)(e.g.: Int32.TryParse)
  3. 实际上写一个解析器,修剪后
    • 检查号码是否为GUID
      • 在某个位置>中是否有' - ' 0?
      • 如果(Guid.TryParse
        • 返回结果
      • 返回字符串(它不能是数字!)
    • 检查数字是否为小数(是否有点?)
      • 尝试使用各种TryParse
      • 转换为单,双,十进制
      • 如果失败则返回字符串
    • 是否以减号开头?
      • 尝试并解析为Int64,然后检查大小并查看适合的位置(< 256 - > ubyte,< 65536 ushort ...)
      • 如果失败则返回字符串
    • 尝试并解析为Int64
      • 如果有效,请检查最小尺寸是否符合
      • 如果失败则可能是整数,但是太大,尝试解析为double,如果失败则返回字符串

答案 2 :(得分:0)

这是我之前写的,可能是一个帮助:

public static Boolean CanCovertTo(this String value, Type type)
{
    var targetType = type.IsNullableType() ? Nullable.GetUnderlyingType(type) : type;

    TypeConverter converter = TypeDescriptor.GetConverter(targetType);
    return converter.IsValid(value);
}

基本思路是,如果您传递字符串和要测试的Type,则可以在尝试隐藏之前检查转换是否有效。

这个设计的问题是TypeConverter.IsValid()只是TypeConverter.CanConvertFrom()的包装器(有一些异常处理)所以你真的没有消除异常处理,但因为它是BCL的一部分,我倾向于认为这将是一个更好的实施。

所以你可以这样实现:

private static Type[] defaultTypes = new Type[]
{
    typeof(System.SByte),
    typeof(System.Byte), 
    typeof(System.Int16), 
    typeof(System.UInt16), 
    typeof(System.Int32), 
    typeof(System.UInt32), 
    typeof(System.Int64), 
    typeof(System.UInt64), 
    typeof(System.Single), 
    typeof(System.Double), 
    typeof(System.Decimal),
    typeof(System.DateTime),
    typeof(System.Guid)
};

public static dynamic ConvertToType(string value)
{
    return ConvertToType(value, defaultTypes);
}

public static dynamic ConvertToType(string value, Type[] types)
{
    foreach (Type type in types)
    {
        if (!value.CanConvertTo(type))
            continue;
        return Convert.ChangeType(value, type);
    }

    return value;
}

如果没有异常处理(即使是TypeConverter.IsValid方法中的异常处理),也没有很好的方法可以做到这一点,所以如果你真的需要这样的方法,你必须忍受它。但是,除了设计中的一些改进之外,如果您在miniBill's answer中实现了一些建议,则可以限制异常处理的需要。

答案 3 :(得分:0)

您可以通过调用Reflection方法使用Parse来处理所有TryParse类型,这比使用ChangeType

public Type[] PredefinedTypes = new Type[]
{
    typeof(System.SByte),
    typeof(System.Byte), 
    typeof(System.Int16), 
    typeof(System.UInt16), 
    typeof(System.Int32), 
    typeof(System.UInt32), 
    typeof(System.Int64), 
    typeof(System.UInt64), 
    typeof(System.Single), 
    typeof(System.Double), 
    typeof(System.Decimal),
    typeof(System.DateTime),
    typeof(System.Guid)
};


public dynamic ConvertToType(string value)
{
    foreach (var predefinedType in PredefinedTypes.Where(t => t.GetMethods().Any(m => m.Name.Equals("TryParse"))))
    {
        var typeInstance = Activator.CreateInstance(predefinedType);
        var methodParamTypes = new Type[] { typeof(string), predefinedType.MakeByRefType() };
        var methodArgs = new object[] { value, typeInstance };
        if ((bool)predefinedType.GetMethod("TryParse", methodParamTypes).Invoke(predefinedType, methodArgs))
        {
            return methodArgs[1];
        }
    }
    return value
}