Как сделать общий парсер чисел в C#?

чтобы разобрать строку на int, вызывается Int32.Parse(string), для двойной, Double.Parse(string), для длинных, Int64.Parse(string) и так далее..

можно ли создать метод, который делает его универсальным, например, ParseString<T>(string)? где T может быть Int32, Double, etc. Я замечаю, что количество типов не реализует какой-либо общий интерфейс, и Parse методы не имеют общего родителя.

есть ли способ достичь этого или чего-то подобного этому?

5 ответов


вам в основном придется использовать отражение, чтобы найти соответствующий статический Parse метод, вызовите его и верните возвращаемое значение в T. Кроме того, вы можете использовать Convert.ChangeType или сделать соответствующий TypeDescriptor и связанного с TypeConverter.

более ограниченным, но эффективным (и простым, в некотором смысле) подходом было бы сохранить словарь от типа до разбора делегата - приведите делегат к Func<string, T> и вызывать его. Это было бы позволяет использовать разные методы для разных типов, но вам нужно знать типы, которые необходимо преобразовать в Up-front.

что бы вы ни делали, вы не сможете указать общее ограничение, которое сделало бы его безопасным во время компиляции. Действительно, вам нужно что-то вроде моей идеи статические интерфейсы для такого рода вещи. EDIT: как уже упоминалось, есть IConvertible интерфейс, но это не обязательно означает, что вы сможете конвертировать от string. Другой тип мог бы реализовать IConvertible без какого-либо способа преобразования в этот тип из строки.


на самом деле, стандартные типы номером do реализовать общий интерфейс: в разделе iconvertible. Это тот, который 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

Я написал код, который использует отражение, чтобы найти 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

но я не тестировал их слишком много, поэтому у них могут быть некоторые ошибки/не работать правильно с каждым типом. Обработка ошибок также немного отсутствует.

и у них нет перегрузок для инвариантной культуры разбор. Так что вам, вероятно, нужно добавить это.


да, типы, которые могут быть проанализированы из строки, скорее всего, будут иметь static Parse и TryParse перегрузки, которые вы можете найти через отражение, как Джон предложил.

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 что также вызывает бокс когда ваш тип является типом значения.

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();
}

вызов скомпилированного лямбда-выражения по существу так же быстр, как вызов самого исходного метода синтаксического анализа, но построение и компиляция его в первую очередь нет. Вот почему, опять же, как предложил Джон, мы должны кэшировать результирующий делегат.

Я использую статический, универсальный класс для кэширования анализаторы в 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);
}