Как сделать общий анализатор чисел на С#?

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

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

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


person Louis Rhys    schedule 28.05.2011    source источник
comment
Как насчет чего-то вроде - If(int.TryParse(...)) else if (double.TryParse....   -  person Maxim    schedule 28.05.2011


Ответы (5)


В основном вам придется использовать отражение, чтобы найти соответствующий статический метод Parse, вызвать его и привести возвращаемое значение обратно к T. В качестве альтернативы вы можете использовать Convert.ChangeType или получить соответствующий < href="http://msdn.microsoft.com/en-us/library/system.componentmodel.typedescriptor.aspx" rel="noreferrer">TypeDescriptor и связанный TypeConverter.

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

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

person Jon Skeet    schedule 28.05.2011
comment
Спасибо, это действительно хорошие обходные пути. Можете ли вы объяснить немного больше для того, кто использует Convert.ChangeType? Ссылки открывал, но так и не понял как это работает. - person Louis Rhys; 28.05.2011
comment
@Louis: вы бы просто вызвали Convert.ChangeType(value, typeof(T)), а затем привели бы возвращаемое значение к T. - person Jon Skeet; 28.05.2011
comment
Ой. Так что плохого в том, чтобы использовать это и поместить в блок try-catch? - person Louis Rhys; 28.05.2011
comment
@Louis: Ничего обязательного - я не уверен, что буду обязательно использовать блок try/catch; Это зависит от контекста. - person Jon Skeet; 28.05.2011

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

К сожалению, нет эквивалента TryParse, он будет генерировать исключения, если строка не может быть проанализирована.

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

person Simon Mourier    schedule 28.05.2011
comment
Что плохого в том, чтобы использовать это и поместить в блок try-catch? Разве это не работает как TryParse? - person Louis Rhys; 28.05.2011
comment
@Louis - Конечно, но не рекомендуется вызывать исключения в обычных путях выполнения (по соображениям производительности и другим причинам). Вот почему все методы TryParse были добавлены к методам Parse. Но нет интерфейса ITryConvertible :/ - person Simon Mourier; 28.05.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
person Brian Chavez    schedule 28.05.2011
comment
Преимущество этого подхода в том, что вы можете иметь не только простые типы, такие как bool, int, string, но и более сложные пользовательские типы. - person Slobodan Savkovic; 29.11.2019

Да, типы, которые можно проанализировать из строки, скорее всего, будут иметь статические перегрузки 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);
}
person Şafak Gür    schedule 18.11.2017

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

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

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

person CodesInChaos    schedule 28.05.2011