. net中的智能类型转换
本文关键字:类型转换 智能 net | 更新日期: 2023-09-27 18:01:45
我一直在尝试将。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中实现得更好?
EDIT:请注意,数组中类型的排序是为了尽可能准确地出现给定类型的"最佳匹配"。
EDIT:作为miniBill的请求,这是如何使用该方法的(简单的例子!):
JsonDictionary["myKey"] = ConvertToType("255"); // 255 is a stringified json value, which should be assigned to myKey as a byte.
你的方法不理想,因为如果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();
你的方法会起作用,但是,正如你所说,它不是那么优雅。
我认为你有两种方法来改进这段代码:
- 将数组移出函数,如psubsee2003所述
- 使用
TryParse
方法进行更便宜的测试(不涉及捕获)(例如:Int32.TryParse
) - 实际上写一个解析器,在修剪之后,
- 检查号码是否为GUID
- 在> 0的位置有'-'吗?
- if(
Guid.TryParse
)- 返回结果
- 返回字符串(不能是数字!)
- 检查数字是否为小数(它是否有一个点在里面?)
- 尝试使用各种
TryParse
转换为单、双、十进制 - 如果失败返回字符串
- 尝试使用各种
- 是否以负号开头?
- 尝试解析为Int64,然后检查大小,看看它适合哪里(<256 -> ubyte, <65536年ushort…)
- 如果失败返回字符串
- 尝试解析为Int64
- 如果它工作检查最小尺寸它适合
- 如果失败,它可能是一个整数,但太大,尝试解析为双精度,如果失败返回字符串
- 检查号码是否为GUID
这是我之前写的一些东西,可能会有所帮助:
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回答中的一些建议,并对设计进行一些改进,则可以限制对异常处理的需求。
您可以使用Reflection
通过调用TryParse
方法来处理所有Parse
类型,这将比使用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
}