如何在C#中制作一个泛型数字解析器

本文关键字:泛型 一个 数字 | 更新日期: 2023-09-27 17:57:48

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

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

有什么方法可以实现这一点或类似的事情吗?

如何在C#中制作一个泛型数字解析器

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

一种更有限但更有效(在某些方面也很简单)的方法是将字典从类型转换为解析委托-将委托转换为Func<string, T>并调用它。这将允许您对不同的类型使用不同的方法,但您需要提前知道需要转换为的类型。

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

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

不幸的是,没有等效的TryParse,如果无法解析字符串,它将抛出异常。

顺便说一句,BCL团队似乎已经完全忘记了整个"转换"领域。自.NET Framework 1以来,这里没有什么新内容(除了TryParse方法)。

这很难理解,但它可以使用Newtonsoft.Json(Json.NET):

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

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

private static Func<string, T> GetParser<T>()
{
    // 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 });
}

性能方面-Invoke方法将方法参数作为object[]接受,这是不必要的分配,如果参数包括值类型,则会导致装箱。它还返回一个object,导致生成的数字(假设它也是一个值类型)被装箱。

为了应对这种情况,您可以构建lambda表达式:

private static Func<string, T> GetParser<T>()
{
    // 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<Func<string, T>>(call, param);
    return lambda.Compile();
}

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

我使用一个静态的泛型类来缓存ValueString中的解析器。

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

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

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

我写了一些代码,使用反射在类型上查找Parse/TryParse方法,并从泛型函数访问这些方法:

private static class ParseDelegateStore<T>
{
    public static ParseDelegate<T> Parse;
    public static TryParseDelegate<T> TryParse;
}
private delegate T ParseDelegate<T>(string s);
private delegate bool TryParseDelegate<T>(string s, out T result);

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

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

但我还没有对它们进行太多测试,所以它们可能会有一些错误/不能正确地与每种类型一起工作。错误处理也有点欠缺。

并且它们没有用于文化不变解析的重载。所以你可能需要补充一下。

相关文章: