如何在C#中创建通用数字解析器?

要将字符串解析为int,可以调用Int32.Parse(string) ,double, Double.Parse(string) ,long, Int64.Parse(string)等等。

是否可以创建一个使其成为通用的方法,例如, ParseString(string) ? 其中T可以是Int32Double等。我注意到类型的数量没有实现任何公共接口,并且Parse方法没有任何公共父级。

有没有办法实现这个或类似的东西?

您基本上必须使用reflection来查找相关的静态Parse方法,调用它,并将返回值强制转换回T 或者,您可以使用Convert.ChangeType或获取相关的TypeDescriptor和关联的TypeConverter

更有限但有效(并且在某些方面更简单)的方法是保持字典从类型到解析委托 – 将委托转换为Func并调用它。 这将允许您对不同类型使用不同的方法,但您需要知道转换为预先所需的类型。

无论你做什么,你都无法指定一个通用的约束,它会在编译时使其安全。 真的,你需要像我这样的静态接口的想法。 编辑:如上所述,有IConvertible接口,但这并不一定意味着你将能够从string转换。 另一种类型可以实现IConvertible而无需从字符串转换为该类型。

实际上,标准数字类型确实实现了一个通用接口: IConvertible 。 这是Convert.ChangeType使用的那个。

不幸的是,没有TryParse等价物,如果无法解析字符串,它将抛出exception。

作为旁注,BCL团队似乎完全忘记了整个“转换”区域。 自.NET Framework 1以来没有什么新东西(除了TryParse方法)。

这非常hackish,但它使用Newtonsoft.Json(Json.NET)

  JsonConvert.DeserializeObject("24.11"); // Type == System.Double - Value: 24.11 JsonConvert.DeserializeObject("29.4"); // Type == System.Int32 - Value: 29 

我编写了一些代码,使用reflection来查找类型上的Parse / TryParse方法,并从generics函数中访问它们:

 private static class ParseDelegateStore { public static ParseDelegate Parse; public static TryParseDelegate TryParse; } private delegate T ParseDelegate(string s); private delegate bool TryParseDelegate(string s, out T result); public static T Parse(string s) { ParseDelegate parse = ParseDelegateStore.Parse; if (parse == null) { parse = (ParseDelegate)Delegate.CreateDelegate(typeof(ParseDelegate), typeof(T), "Parse", true); ParseDelegateStore.Parse = parse; } return parse(s); } public static bool TryParse(string s, out T result) { TryParseDelegate tryParse = ParseDelegateStore.TryParse; if (tryParse == null) { tryParse = (TryParseDelegate)Delegate.CreateDelegate(typeof(TryParseDelegate), typeof(T), "TryParse", true); ParseDelegateStore.TryParse = tryParse; } return tryParse(s, out result); } 

https://github.com/CodesInChaos/ChaosUtil/blob/master/Chaos.Util/Conversion.cs

但我没有对它们进行过多的测试,所以它们可能会因为每种类型都有一些错误/不能正常工作。 error handling也有点缺乏。

并且他们没有文化不变解析的重载。 所以你可能需要添加它。

是的,可以从字符串中解析的类型很可能具有静态ParseTryParse重载,您可以通过Jon建议的reflection找到这些重载。

 private static Func GetParser() { // The method we are searching for accepts a single string. // You can add other types, like IFormatProvider to target specific overloads. var signature = new[] { typeof(string) }; // Get the method with the specified name and parameters. var method = typeof(T).GetMethod("Parse", signature); // Initialize the parser delegate. return s => (T)method.Invoke(null, new[] { s }); } 

为了性能,您还可以构建调用它们的lambda表达式,因为Invoke方法接受方法参数作为object[] ,这是一个不必要的分配,如果您的参数包含值类型,则导致装箱。 它还将结果作为object返回,当您的类型为值类型时,该object也会导致装箱。

 private static Func GetParser() { // Get the method like we did before. var signature = new[] { typeof(string) }; var method = typeof(T).GetMethod("Parse", signature); // Build and compile a lambda expression. var param = Expression.Parameter(typeof(string)); var call = Expression.Call(method, param); var lambda = Expression.Lambda>(call, param); return lambda.Compile(); } 

调用已编译的lambda表达式与调用原始解析方法本身一样快,但首先构建和编译它不是。 这就是为什么,像Jon建议的那样,我们应该缓存生成的委托。

我使用静态generics类来缓存ValueString中的解析器。

 private static class Parser { public static readonly Func Parse = InitParser(); private static Func InitParser() { // Our initialization logic above. } } 

之后,您的解析方法可以这样写:

 public static T Parse(string s) { return Parser.Parse(s); }