作者:eaglet
转载请保留原作者署名并注明出处。
我们日常工作中经常会遇到将字符串转换为值类型的问题,比如"123" 转换为一个Int32类型,这个很简单,我们只要调用int.Parse 函数就可以实现。eaglet 今天要说的是,一些应用中往往在编码阶段无法知道字符串需要转换成的值类型的具体类型,而需要在运行时动态指定类型进行转换,遇到这种问题,我们如何解决?
一个典型的例子。
如下函数,我们只知道value 是一个基本值类型,比如(System.Int32, System.Int16, System.Double 等等) 或者是一个字符串类型。但我们无法在编码时确定这个value 具体是什么类型,它可能是由一个外部组件从某个数据源中读出来的基本类型中的一种类型的实例或者是字符串类型实例。
这个函数希望实现比较value 是否在minValue, maxValue这两个字符串对应的数值区间类,其中minValue 和 maxValue 构成一个闭区间,即
value in [minValue, maxValue]
public static bool MyComparer(object value, string minValue, string maxValue)
要解决这个问题,我们需要解决两个基本问题。
1. 如何比较value 和 minValue, maxValue
2. 如何将minValue 和 maxValue 转换到value 对应的数据类型
首先我们来看如何进行比较
所有的基本值类型和string 类型都实现 IComparable这个接口。我们可以指定 value 为 IComparable,然后调用CompareTo来进行比较。不过这里有个问题,CompareTo 函数的参数obj 虽然是一个 object 类型,但这个 obj 的类型必须和 value 一致,否则将发生异常。也就是说我们不能把minValue 任意转换成某个类型比如 long 带进去,而需要将 minValue 和 maxValue 转换成和 value 一样的类型才行。
下面我们就来讨论如何将字符串转换为指定的值类型。
我们需要实现下面的函数:
public static object ToType(Type type, string value)
type 为指定的类型,value 为输入的字符串。
首先我们知道所有的基本值类型都有一个叫 Parse 静态函数,我们只要把这个静态函数反射出来,就可以通过这个静态函数将字符串转换成对应的值类型。
下面代码给出如何反射出这个静态函数。通过向对象type(Type类型)的GetMethods 函数输入 BindingFlags.Static
| BindingFlags.Public 参数,我们可以枚举出这个类型所有的静态公共函数。
然后我们判断这个函数的名称是否为 "Parse" ,由于 Parse 函数有多个重载,但一个参数的重载只有 Parse (String)
所以我们需要判断mi 只有一个参数,这时取到的 mi 就是 Parse (String) 函数。
foreach (MethodInfo mi in type.GetMethods(BindingFlags.Static
| BindingFlags.Public))
{
if (mi.Name == "Parse" && mi.GetParameters().Length == 1)
{
parseMethod = mi;
break;
}
}
接下来就是如何调用这个函数来转换字符串了。
如下面代码,我们调用MethodInfo 的 Invoke 方法来动态调用这个函数,由于是静态函数,第一个参数 obj 传入null. 并在第二个参数中带入value 这个参数。
下面给出字符串转换为指定类型的完整代码
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace Eagelt.Convert
{
public class ConvertString
{
public static object ToType(Type type, string value)
{
if (type == typeof(string))
{
return value;
}
MethodInfo parseMethod = null;
foreach (MethodInfo mi in type.GetMethods(BindingFlags.Static
| BindingFlags.Public))
{
if (mi.Name == "Parse" && mi.GetParameters().Length == 1)
{
parseMethod = mi;
break;
}
}
if (parseMethod == null)
{
throw new ArgumentException(string.Format(
"Type: {0} has not Parse static method!", type));
}
return parseMethod.Invoke(null, new object[] { value });
}
}
}
{
public static object ToType(Type type, string value)
{
return System.ComponentModel.TypeDescriptor.GetConverter(type).ConvertFrom(value);
}
}
MyComparer 函数的完整代码
{
IComparable comparableObj = value as IComparable;
object min;
object max;
if (comparableObj == null)
{
throw new ArgumentException(string.Format(
"Type: {0} does not inherit from IComparable", value.GetType()));
}
min = ConvertString.ToType(value.GetType(), minValue);
max = ConvertString.ToType(value.GetType(), maxValue);
return comparableObj.CompareTo(min) >= 0 && comparableObj.CompareTo(max) <= 0;
}
测试代码
Console.WriteLine(MyComparer(3, "1", "2"));
Console.WriteLine(MyComparer((byte)2, "1", "3"));
Console.WriteLine(MyComparer((double)3, "1", "2"));
Console.WriteLine(MyComparer("3", "1", "2"));
测试结果
True
False
True
False
False
最后提一个简单的问题,供大家思考,如果我们需要将字符串转换成我们自己定义的复杂类型,如何实现呢?
比如我们有一个结构
struct UInt128
{
public ulong Low;
public ulong High;
}
我们将这个结构的实例对象传入到函数
Console.WriteLine(MyComparer((UInt128)2, "1", "3"));
这样可以吗?怎样做才能做到呢?