【问题标题】:How to make a generic number parser in C#? [duplicate]如何在 C# 中制作通用数字解析器? [复制]
【发布时间】:2011-09-03 20:34:46
【问题描述】:

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

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

有什么方法可以实现这个或类似的东西吗?

【问题讨论】:

  • 怎么样 - If(int.TryParse(...)) else if (double.TryParse ....

标签: c# .net string parsing numbers


【解决方案1】:

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

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

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

【讨论】:

  • 谢谢,这些都是非常好的解决方法。你能解释一下使用 Convert.ChangeType 的那个吗?打开链接,但我仍然不明白它是如何工作的。
  • @Louis:您只需调用Convert.ChangeType(value, typeof(T)),然后将返回值转换为T
  • 哦。那么使用它并将其包装在 try-catch 块中有什么问题?
  • @Louis:没必要——我不确定我什至会使用 try/catch 块;这取决于上下文。
【解决方案2】:

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

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

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

【讨论】:

  • 使用它并将其包装在 try-catch 块中有什么问题?它的功能不像 TryParse 吗?
  • @Louis - 当然,但不建议在正常执行路径中引发异常(出于性能和其他原因)。这就是为什么所有的 TryParse 方法都被添加到 Parse 方法中的原因。但是没有“ITryConvertible”接口:/
【解决方案3】:

这很 hackish,但可以使用 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

【讨论】:

  • 这种方法的好处是,您不仅可以拥有 bool、int、string 等简单类型,还可以拥有更复杂的自定义类型。
【解决方案4】:

是的,可以从字符串中解析的类型很可能具有静态 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);
}

【讨论】:

    【解决方案5】:

    我编写了一些代码,使用反射来查找类型上的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

    但我没有对它们进行过多测试,因此它们可能会出现一些错误/无法在每种类型上正常工作。错误处理也有点欠缺。

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

    【讨论】:

      猜你喜欢
      • 2023-03-23
      • 2015-02-02
      • 1970-01-01
      • 1970-01-01
      • 2019-11-25
      • 1970-01-01
      • 2010-10-12
      • 2012-03-09
      • 1970-01-01
      相关资源
      最近更新 更多