1

我一直在尝试将字符串转换为 .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

4 回答 4

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();
于 2013-03-02T12:54:18.110 回答
0

你的方法会奏效,但正如你所说,它并不那么优雅。

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

  1. 正如 psubsee2003 所说,将数组移出函数
  2. 使用TryParse更便宜的测试方法(不涉及捕获)(例如Int32.TryParse:)
  3. 实际上写一个解析器,在修剪之后,
    • 检查号码是否为 GUID
      • 它在 > 0 的位置是否有“-”?
      • 如果(Guid.TryParse
        • 返回结果
      • 返回字符串(不能是数字!)
    • 检查数字是否为小数(是否有一个点?)
      • 尝试使用各种转换为单、双、十进制TryParse
      • 如果失败返回字符串
    • 它是否以减号开头?
      • 尝试解析为 Int64,然后检查大小并查看是否适合(<256 -> ubyte,< 65536 ushort...)
      • 如果失败返回字符串
    • 尝试解析为 Int64
      • 如果它有效,请检查它适合的最小尺寸
      • 如果失败,它可能是一个整数,但太大,尝试解析为双精度,如果失败返回字符串
于 2013-03-02T11:36:53.570 回答
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要测试的字符串和 a,您可以在尝试转换之前检查转换是否有效。

这种设计的问题在于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 回答中的一些建议以及设计中的一些改进,则可以限制对异常处理的需求。

于 2013-03-02T11:56:47.800 回答
0

您可以通过调用该方法Reflection来处理所有类型,这比使用处理多个异常要快一些ParseTryParseChangeType

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
}
于 2013-03-02T12:08:12.873 回答