Как сделать общий анализатор чисел в C #? - PullRequest
10 голосов
/ 28 мая 2011

Чтобы разобрать строку в int, вызывается Int32.Parse(string), для double, Double.Parse(string), для long, Int64.Parse(string) и т. Д.

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

Есть ли способ достичь этого или что-то похожее на это?

Ответы [ 5 ]

12 голосов
/ 28 мая 2011

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

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

Что бы вы ни делали, вы не сможете указать общийограничение, которое сделает его безопасным во время компиляции, хотя.На самом деле вам нужно что-то вроде моей идеи статических интерфейсов для такого рода вещей.РЕДАКТИРОВАТЬ: Как уже упоминалось, есть интерфейс IConvertible, но это не обязательно означает, что вы сможете конвертировать из string.Другой тип может реализовать IConvertible без какого-либо способа преобразования в этот тип из строки.

4 голосов
/ 28 мая 2011

На самом деле стандартные типы чисел do реализуют общий интерфейс: IConvertible .Это тот, который Convert.ChangeType использует.

К сожалению, нет эквивалента TryParse, он будет выдавать исключения, если строку невозможно проанализировать.Вся эта область "конверсии" была полностью забыта командой BCL.Там нет ничего нового с .NET Framework 1 (кроме методов TryParse).

1 голос
/ 28 мая 2011

Это очень хакерский, но работает с использованием 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
0 голосов
/ 18 ноября 2017

Да, типы, которые можно анализировать из строки, скорее всего, будут иметь статические перегрузки 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);
}
0 голосов
/ 28 мая 2011

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

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

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

...