Загальний TryParse


196

Я намагаюся створити загальне розширення, яке використовує 'TryParse', щоб перевірити, чи є рядок заданого типу:

public static bool Is<T>(this string input)
{
    T notUsed;
    return T.TryParse(input, out notUsed);
}

це не компілюється, оскільки він не може вирішити символ "TryParse"

Як я розумію, "TryParse" не є частиною жодного інтерфейсу.

Чи можливо це взагалі зробити?

Оновлення:

Використовуючи відповіді нижче, я придумав:

public static bool Is<T>(this string input)
{
    try
    {
        TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(input);
    }
    catch
    {
        return false;
    }

    return true;
}

Це працює досить добре, але я думаю, що використовувати винятки таким чином, мені не до душі.

Оновлення2:

Змінено для передачі типу, а не використання дженерики:

public static bool Is(this string input, Type targetType)
{
    try
    {
        TypeDescriptor.GetConverter(targetType).ConvertFromString(input);
        return true;
    }
    catch
    {
        return false;
    }
}

1
Я думаю, що в цьому загальному випадку вам просто доведеться мати справу з винятком хитрості. ви можете додати випадки, щоб перевірити, чи є ints або double, а потім скористатись певними методами TryParse, але вам все одно доведеться повертатися до цього, щоб спіймати інші типи.
luke

1
Використання родового зайве. Просто переведіть у параметрі Тип як параметр. загальнодоступне статичне bool Є (це рядок введення, Type targetType). Таким чином, дзвінок виглядає трохи красивіше: x.Is (typeof (int)) -VS- x.Is <int> ()
mikesigs

2
На конверторі існує метод IsValid, щоб перевірити, чи будуть проблеми з конверсією. Я використав наведений нижче метод і, здається, працює добре. protected Boolean TryParse<T>(Object value, out T result) { result = default(T); var convertor = TypeDescriptor.GetConverter(typeof(T)); if (convertor == null || !convertor.IsValid(value)) { return false; } result = (T)convertor.ConvertFrom(value); return true; }
CastroXXL

@CastroXXL Дякую, що проявили інтерес до цього питання, однак ваш метод не дуже працює, тому що я хотів перевірити, чи є значення рядка певного типу, а не об’єктним, хоча ваш метод був би корисний для типів об'єктів (але доведеться загортати ConvertFrom(value)метод у try-catchблок, щоб знайти винятки
Пірс Майєрс

2
Ви повинні перевірити, чи є (targetType == null), оскільки перше використання його у вашому коді може кинутись, але цей виняток проковтне ваш улов.
Нік Струпат

Відповіді:


183

Вам слід використовувати клас TypeDescriptor :

public static T Convert<T>(this string input)
{
    try
    {
        var converter = TypeDescriptor.GetConverter(typeof(T));
        if(converter != null)
        {
            // Cast ConvertFromString(string text) : object to (T)
            return (T)converter.ConvertFromString(input);
        }
        return default(T);
    }
    catch (NotSupportedException)
    {
        return default(T);
    }
}

3
Вибачте, що воскресив, але чи повертає GetConverter нульове значення? Я думаю, якщо це було тоді, ймовірно, слід викинути виняток, а не по суті мовчки вийти з ладу і повернути щось інше. Коли я спробував це на своєму власному класі (в якому я не визначав typeconverter), я отримав конвертер від GetConverter, але потім ConvertFromString кинув NotSupportedException.
user420667

3
@ user420667, я вважаю, що вам слід перевірити результат CanConvertFrom (typeof (string)), перш ніж намагатися перетворити з рядка. TypeConverter може не підтримувати перетворення з рядка.
Рубін Бонд

3
Ви можете додати if (typeof (T) .IsEnum) {return (T) Enum.Parse (typeof (T), input); } [як досить загальний ярлик для всіх типів Enum] перед тим, як отримати конвертер. Я думаю, це залежить від того, як часто ви будете робити типи Enum на відміну від більш складних типів.
Джессі Чісгольм

10
Я не розумію, чому це позначається як відповідь і настільки високо оцінюється, коли він не реалізує те, що було запропоновано: загальний Try Parse. Основна мета методів TryParse полягає в тому, що вони не викидають винятків під час спроби виконувати синтаксичний аналіз і надають набагато менший вплив на продуктивність, коли розбір не вдається, і це рішення не забезпечує лише цього.
Флорін Думітреску

2
Одне питання w / це те, що якщо T - int, а вхід більше, ніж int.MaxValue, він викине System.Exception w / System.OverFlowException як внутрішній виняток. Отже, якщо ви очікуєте OverflowException, ви не отримаєте його, якщо ви не допитаєте закинутий виняток. Причина полягає в тому, що ConvertFromString кидає OverflowException, а потім передача на T кидає System.Exception.
Тревор

78

Нещодавно мені також потрібен загальний TryParse. Ось що я придумав;

public static T? TryParse<T>(string value, TryParseHandler<T> handler) where T : struct
{
    if (String.IsNullOrEmpty(value))
        return null;
    T result;
    if (handler(value, out result))
        return result;
    Trace.TraceWarning("Invalid value '{0}'", value);
    return null;
}

public delegate bool TryParseHandler<T>(string value, out T result);

Тоді це просто питання зателефонувати таким чином:

var value = TryParse<int>("123", int.TryParse);
var value2 = TryParse<decimal>("123.123", decimal.TryParse);

3
Щойно я знову наткнувся на цю публікацію через кілька місяців і під час її використання знову помітив, що метод не може зробити висновок Tз обробника, і ми повинні чітко вказати, Tколи ми його називаємо. Мені цікаво, чому він не може зробити висновок T?
Нік Струпат

25
Чому ви хочете використовувати цю функцію? Якщо ви знаєте, яку функцію викликати для розбору значення, чому б просто не зателефонувати безпосередньо? Він уже знає правильний тип введення і немає необхідності в generics. Це рішення не працюватиме для типів без TryParseHandler.
xxbbcc

2
@xxbbcc: Я хотів би скористатися цією функцією, оскільки TryParse повертає булеве значення, яке вказує на те, що аналіз був успішним. Він повертає ваше проаналізоване значення через вихідний параметр. Іноді просто хочеться зробити щось подібне SomeMethod(TryParse<int>(DollarTextbox.Text, int.TryParse))без створення вихідної змінної, щоб отримати результат int.TryParse. Однак я погоджуюсь з думками Ніка щодо того, що функція визначає тип.
Вальтер Стабош

1
Дуже ефективний метод. Настійно рекомендується.
Володимир Кочанчич

3
Я б рекомендував значення за замовчуванням як третій параметр. Це вирішує проблему, коли T не можна зробити висновок. Крім того, це дозволяє вирішити, яке значення вони хочуть, якщо значення рядка недійсне. Наприклад, -1 може означати недійсний. публічна статична T TryParse <T> (значення рядка, обробник TryParseHandler <T>, T за замовчуваннямValue)
Rhyous

33

Використання try / catch для контролю потоку - жахлива політика. Викидання виключення спричиняє відставання продуктивності, тоді як час виконання працює навколо винятку. Натомість перевірити дані перед перетворенням.

var attemptedValue = "asdfasdsd";
var type = typeof(int);
var converter = TypeDescriptor.GetConverter(type);
if (converter != null &&  converter.IsValid(attemptedValue))
    return converter.ConvertFromString(attemptedValue);
else
    return Activator.CreateInstance(type);

2
Я отримую повідомлення про Resharper, що converter != nullзавжди відповідає дійсності, тому його можна видалити з коду.
ErikE

5
@ErikE Я не завжди довіряю цим застереженням ReSharper. Часто вони не бачать, що відбувається під час виконання.
ПрофК

1
@ProfK MSDN не говорить, що він може повернути нулеве msdn.microsoft.com/en-us/library/ewtxwhzx.aspx
danio

@danio Я взагалі просто ділився своїм досвідом таких попереджень R #. Я, звичайно, не мав на увазі, що в цьому випадку це було неправильно.
ПрофК

14

Якщо ви налаштовані на використання TryParse, ви можете використовувати відображення і робити це так:

public static bool Is<T>(this string input)
{
    var type = typeof (T);
    var temp = default(T);
    var method = type.GetMethod(
        "TryParse",
        new[]
            {
                typeof (string),
                Type.GetType(string.Format("{0}&", type.FullName))
            });
    return (bool) method.Invoke(null, new object[] {input, temp});
}

Це дуже круто, і він позбавляється тих винятків, які мені все одно не сподобалися. І все-таки трохи згорнувшись.
Пірс Майєрс

6
Приємне рішення, але будь-яка відповідь, що включає рефлексію (особливо у корисному методі, який легко можна викликати із внутрішнього циклу) потребує відмови від відповідальності щодо продуктивності. Дивіться: stackoverflow.com/questions/25458/how-costly-is-net-reflection
Patrick M

Зітхнути. Отже, вибір (1) використання винятків для управління потоком коду, (2) використання відображення з його швидкісними витратами. Я згоден з @PiersMyers - жоден вибір не є ідеальним. Добре, що вони обоє працюють. :)
Джессі Чизгольм

Я думаю , ви можете замінити Type.GetType(string.Format(...))з type.MakeByRefType().
Дрю Ноакс

3
метод потрібно відображати лише один раз на тип, а не один раз на виклик. якщо ви робите це загальним класом зі змінною статичного члена, то ви можете повторно використовувати результат першого відображення.
Ендрю Хілл

7

Тут використовується статичний конструктор для кожного загального типу, тому він повинен виконувати дорогу роботу лише в перший раз, коли ви зателефонуєте йому на заданий тип. Він обробляє всі типи в просторі імен системи, які мають TryParse методи. Він також працює з нульовими версіями кожної з тих (що є конструкціями) за винятком перерахувань.

    public static bool TryParse<t>(this string Value, out t result)
    {
        return TryParser<t>.TryParse(Value.SafeTrim(), out result);
    }
    private delegate bool TryParseDelegate<t>(string value, out t result);
    private static class TryParser<T>
    {
        private static TryParseDelegate<T> parser;
        // Static constructor:
        static TryParser()
        {
            Type t = typeof(T);
            if (t.IsEnum)
                AssignClass<T>(GetEnumTryParse<T>());
            else if (t == typeof(bool) || t == typeof(bool?))
                AssignStruct<bool>(bool.TryParse);
            else if (t == typeof(byte) || t == typeof(byte?))
                AssignStruct<byte>(byte.TryParse);
            else if (t == typeof(short) || t == typeof(short?))
                AssignStruct<short>(short.TryParse);
            else if (t == typeof(char) || t == typeof(char?))
                AssignStruct<char>(char.TryParse);
            else if (t == typeof(int) || t == typeof(int?))
                AssignStruct<int>(int.TryParse);
            else if (t == typeof(long) || t == typeof(long?))
                AssignStruct<long>(long.TryParse);
            else if (t == typeof(sbyte) || t == typeof(sbyte?))
                AssignStruct<sbyte>(sbyte.TryParse);
            else if (t == typeof(ushort) || t == typeof(ushort?))
                AssignStruct<ushort>(ushort.TryParse);
            else if (t == typeof(uint) || t == typeof(uint?))
                AssignStruct<uint>(uint.TryParse);
            else if (t == typeof(ulong) || t == typeof(ulong?))
                AssignStruct<ulong>(ulong.TryParse);
            else if (t == typeof(decimal) || t == typeof(decimal?))
                AssignStruct<decimal>(decimal.TryParse);
            else if (t == typeof(float) || t == typeof(float?))
                AssignStruct<float>(float.TryParse);
            else if (t == typeof(double) || t == typeof(double?))
                AssignStruct<double>(double.TryParse);
            else if (t == typeof(DateTime) || t == typeof(DateTime?))
                AssignStruct<DateTime>(DateTime.TryParse);
            else if (t == typeof(TimeSpan) || t == typeof(TimeSpan?))
                AssignStruct<TimeSpan>(TimeSpan.TryParse);
            else if (t == typeof(Guid) || t == typeof(Guid?))
                AssignStruct<Guid>(Guid.TryParse);
            else if (t == typeof(Version))
                AssignClass<Version>(Version.TryParse);
        }
        private static void AssignStruct<t>(TryParseDelegate<t> del)
            where t: struct
        {
            TryParser<t>.parser = del;
            if (typeof(t).IsGenericType
                && typeof(t).GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                return;
            }
            AssignClass<t?>(TryParseNullable<t>);
        }
        private static void AssignClass<t>(TryParseDelegate<t> del)
        {
            TryParser<t>.parser = del;
        }
        public static bool TryParse(string Value, out T Result)
        {
            if (parser == null)
            {
                Result = default(T);
                return false;
            }
            return parser(Value, out Result);
        }
    }

    private static bool TryParseEnum<t>(this string Value, out t result)
    {
        try
        {
            object temp = Enum.Parse(typeof(t), Value, true);
            if (temp is t)
            {
                result = (t)temp;
                return true;
            }
        }
        catch
        {
        }
        result = default(t);
        return false;
    }
    private static MethodInfo EnumTryParseMethod;
    private static TryParseDelegate<t> GetEnumTryParse<t>()
    {
        Type type = typeof(t);

        if (EnumTryParseMethod == null)
        {
            var methods = typeof(Enum).GetMethods(
                BindingFlags.Public | BindingFlags.Static);
            foreach (var method in methods)
                if (method.Name == "TryParse"
                    && method.IsGenericMethodDefinition
                    && method.GetParameters().Length == 2
                    && method.GetParameters()[0].ParameterType == typeof(string))
                {
                    EnumTryParseMethod = method;
                    break;
                }
        }
        var result = Delegate.CreateDelegate(
            typeof(TryParseDelegate<t>),
            EnumTryParseMethod.MakeGenericMethod(type), false)
            as TryParseDelegate<t>;
        if (result == null)
            return TryParseEnum<t>;
        else
            return result;
    }

    private static bool TryParseNullable<t>(string Value, out t? Result)
        where t: struct
    {
        t temp;
        if (TryParser<t>.TryParse(Value, out temp))
        {
            Result = temp;
            return true;
        }
        else
        {
            Result = null;
            return false;
        }
    }

6

Як щодо чогось подібного?

http://madskristensen.net/post/Universal-data-type-checker.aspx ( Архів )

/// <summary> 
/// Checks the specified value to see if it can be 
/// converted into the specified type. 
/// <remarks> 
/// The method supports all the primitive types of the CLR 
/// such as int, boolean, double, guid etc. as well as other 
/// simple types like Color and Unit and custom enum types. 
/// </remarks> 
/// </summary> 
/// <param name="value">The value to check.</param> 
/// <param name="type">The type that the value will be checked against.</param> 
/// <returns>True if the value can convert to the given type, otherwise false. </returns> 
public static bool CanConvert(string value, Type type) 
{ 
    if (string.IsNullOrEmpty(value) || type == null) return false;
    System.ComponentModel.TypeConverter conv = System.ComponentModel.TypeDescriptor.GetConverter(type);
    if (conv.CanConvertFrom(typeof(string)))
    { 
        try 
        {
            conv.ConvertFrom(value); 
            return true;
        } 
        catch 
        {
        } 
     } 
     return false;
  }

Це можна перетворити на загальний метод досить легко.

 public static bool Is<T>(this string value)
 {
    if (string.IsNullOrEmpty(value)) return false;
    var conv = System.ComponentModel.TypeDescriptor.GetConverter(typeof(T));

    if (conv.CanConvertFrom(typeof(string)))
    { 
        try 
        {
            conv.ConvertFrom(value); 
            return true;
        } 
        catch 
        {
        } 
     } 
     return false;
}

Чи має значення, повертаєте ви істинну з блоку спробу чи повертаєте помилкову з блоку лову? Я гадаю, що ні, але я все ще думаю, що використання винятків таким чином почуває себе неправильно ...
Пірс Майєрс

3
Не має значення, повертаєтесь ви з блоку лову, це те саме. btw. Зазвичай це погано мати загальний пункт улов: catch { }. Однак у цьому випадку немає альтернативи, оскільки .NET BaseNumberConverterкидає Exceptionбазовий клас у разі помилки перетворення. Це дуже прикро. Насправді є ще досить багато місць, коли цей базовий тип кинутий. Сподіваємось, Microsoft виправить це у майбутній версії основи.
Стівен

Дякую, Стівен, не міг би сказати це краще
Боб

Не використовуйте результат перетворення: код є зайвим.
BillW

4

Не можна це робити на загальних типах.

Що ви можете зробити, це створити інтерфейс ITryParable і використовувати його для користувацьких типів, які реалізують цей інтерфейс.

Я думаю, що, якщо ви маєте намір використовувати це з базовими типами, як intі DateTime. Ви не можете змінити ці типи для впровадження нових інтерфейсів.


1
Цікаво, чи вдасться це використати динамічне ключове слово в .net 4?
П’єр-Ален Vigeant

@Pierre: Це не працює за замовчуванням у C # з dynamicключовим словом, оскільки воно не працюватиме при статичному введенні. Ви можете створити свій власний динамічний об’єкт, який може це впоратися, але це не за замовчуванням.
Стівен

4

Надихнувшись рішенням, розміщеним тут Чарлі Брауном, я створив загальний TryParse, використовуючи відображення, яке необов'язково виводить проаналізовані значення:

/// <summary>
/// Tries to convert the specified string representation of a logical value to
/// its type T equivalent. A return value indicates whether the conversion
/// succeeded or failed.
/// </summary>
/// <typeparam name="T">The type to try and convert to.</typeparam>
/// <param name="value">A string containing the value to try and convert.</param>
/// <param name="result">If the conversion was successful, the converted value of type T.</param>
/// <returns>If value was converted successfully, true; otherwise false.</returns>
public static bool TryParse<T>(string value, out T result) where T : struct {
    var tryParseMethod = typeof(T).GetMethod("TryParse", BindingFlags.Static | BindingFlags.Public, null, new [] { typeof(string), typeof(T).MakeByRefType() }, null);
    var parameters = new object[] { value, null };

    var retVal = (bool)tryParseMethod.Invoke(null, parameters);

    result = (T)parameters[1];
    return retVal;
}

/// <summary>
/// Tries to convert the specified string representation of a logical value to
/// its type T equivalent. A return value indicates whether the conversion
/// succeeded or failed.
/// </summary>
/// <typeparam name="T">The type to try and convert to.</typeparam>
/// <param name="value">A string containing the value to try and convert.</param>
/// <returns>If value was converted successfully, true; otherwise false.</returns>
public static bool TryParse<T>(string value) where T : struct {
    T throwaway;
    var retVal = TryParse(value, out throwaway);
    return retVal;
}

Це можна назвати таким чином:

string input = "123";
decimal myDecimal;

bool myIntSuccess = TryParse<int>(input);
bool myDecimalSuccess = TryParse<decimal>(input, out myDecimal);

Оновлення:
Також завдяки рішенню YotaXP, яке мені дуже подобається, я створив версію, яка не використовує методи розширення, але все ще має сингл, мінімізуючи необхідність робити рефлексію:

/// <summary>
/// Provides some extra parsing functionality for value types.
/// </summary>
/// <typeparam name="T">The value type T to operate on.</typeparam>
public static class TryParseHelper<T> where T : struct {
    private delegate bool TryParseFunc(string str, out T result);

    private static TryParseFunc tryParseFuncCached;

    private static TryParseFunc tryParseCached {
        get {
            return tryParseFuncCached ?? (tryParseFuncCached = Delegate.CreateDelegate(typeof(TryParseFunc), typeof(T), "TryParse") as TryParseFunc);
        }
    }

    /// <summary>
    /// Tries to convert the specified string representation of a logical value to
    /// its type T equivalent. A return value indicates whether the conversion
    /// succeeded or failed.
    /// </summary>
    /// <param name="value">A string containing the value to try and convert.</param>
    /// <param name="result">If the conversion was successful, the converted value of type T.</param>
    /// <returns>If value was converted successfully, true; otherwise false.</returns>
    public static bool TryParse(string value, out T result) {
        return tryParseCached(value, out result);
    }

    /// <summary>
    /// Tries to convert the specified string representation of a logical value to
    /// its type T equivalent. A return value indicates whether the conversion
    /// succeeded or failed.
    /// </summary>
    /// <param name="value">A string containing the value to try and convert.</param>
    /// <returns>If value was converted successfully, true; otherwise false.</returns>
    public static bool TryParse(string value) {
        T throwaway;
        return TryParse(value, out throwaway);
    }
}

Назвіть це так:

string input = "987";
decimal myDecimal;

bool myIntSuccess = TryParseHelper<int>.TryParse(input);
bool myDecimalSuccess = TryParseHelper<decimal>.TryParse(input, out myDecimal);

3

Трохи запізнився на вечірку, але ось що я придумав. Немає винятків, одноразове (за типом) рефлексія.

public static class Extensions {
    public static T? ParseAs<T>(this string str) where T : struct {
        T val;
        return GenericHelper<T>.TryParse(str, out val) ? val : default(T?);
    }
    public static T ParseAs<T>(this string str, T defaultVal) {
        T val;
        return GenericHelper<T>.TryParse(str, out val) ? val : defaultVal;
    }

    private static class GenericHelper<T> {
        public delegate bool TryParseFunc(string str, out T result);

        private static TryParseFunc tryParse;
        public static TryParseFunc TryParse {
            get {
                if (tryParse == null)
                    tryParse = Delegate.CreateDelegate(
                        typeof(TryParseFunc), typeof(T), "TryParse") as TryParseFunc;
                return tryParse;
            }
        }
    }
}

Додатковий клас необхідний, оскільки в загальних класах не дозволяються методи розширення. Це дозволяє просте використання, як показано нижче, і вражає відображення лише при першому використанні типу.

"5643".ParseAs<int>()

3

Ось ще один варіант.

Я написав клас, який спрощує реєстрацію будь-якої кількості TryParseобробників. Це дозволяє мені це:

var tp = new TryParser();

tp.Register<int>(int.TryParse);
tp.Register<decimal>(decimal.TryParse);
tp.Register<double>(double.TryParse);

int x;
if (tp.TryParse("42", out x))
{
    Console.WriteLine(x);
};

Мені 42надруковується на консолі.

Клас:

public class TryParser
{
    public delegate bool TryParseDelegate<T>(string s, out T result);

    private Dictionary<Type, Delegate> _tryParsers = new Dictionary<Type, Delegate>();

    public void Register<T>(TryParseDelegate<T> d)
    {
        _tryParsers[typeof(T)] = d;
    }

    public bool Deregister<T>()
    {
        return _tryParsers.Remove(typeof(T));
    }

    public bool TryParse<T>(string s, out T result)
    {
        if (!_tryParsers.ContainsKey(typeof(T)))
        {
            throw new ArgumentException("Does not contain parser for " + typeof(T).FullName + ".");
        }
        var d = (TryParseDelegate<T>)_tryParsers[typeof(T)];
        return d(s, out result);
    }
}

Мені це подобається, але як би ти це зробив без дженериків. Звичайно, використовуйте випадки відображення.
Синестетичний

Я додав перевантажений метод, який робить певну рефлексію. Якщо є більш елегантний спосіб вирішити це питання, я всі очі lol gist.github.com/dasjestyr/90d8ef4dea179a6e08ddd85e0dacbc94
Sineesthetic

2

Коли я хотів зробити майже цю точну річ, мені довелося реалізувати це важким шляхом, враховуючи роздуми. Дано T, подумайте typeof(T)і шукайте метод TryParseчи Parseметод, посилаючись на нього, якщо ви знайшли його.


Це те, що я збирався запропонувати.
Стівен Еверс

2

Це моя спроба. Я робив це як "вправу". Я намагався зробити його таким же подібним до використання, що і існуючі " Convert.ToX () " -они тощо. Але це метод розширення:

    public static bool TryParse<T>(this String str, out T parsedValue)
    {
        try
        {
            parsedValue = (T)Convert.ChangeType(str, typeof(T));
            return true;
        }

        catch { parsedValue = default(T); return false; }
    }

Основним недоліком цього порівняно з TypeConverter.ConvertFrom()тим, що вихідний клас повинен забезпечити перетворення типів, що, як правило, означає, що ви не можете підтримувати перетворення у власні типи.
Ян Голдбі

1

Як ви вже говорили, TryParseце не частина інтерфейсу. Він також не є членом жодного базового класу, оскільки він є насправді staticіstatic функцій не може бути virtual. Таким чином, компілятор не може переконатись, що Tнасправді має член, який називається TryParse, тому це не працює.

Як сказав @Mark, ви можете створити власний інтерфейс і використовувати власні типи, але вам не пощастить для вбудованих типів.


1
public static class Primitive
{
    public static DateTime? TryParseExact(string text, string format, IFormatProvider formatProvider = null, DateTimeStyles? style = null)
    {
        DateTime result;
        if (DateTime.TryParseExact(text, format, formatProvider, style ?? DateTimeStyles.None, out result))
            return result;
        return null;
    }

    public static TResult? TryParse<TResult>(string text) where TResult : struct
    {
        TResult result;
        if (Delegates<TResult>.TryParse(text, out result))
            return result;
        return null;
    }

    public static bool TryParse<TResult>(string text, out TResult result) => Delegates<TResult>.TryParse(text, out result);

    public static class Delegates<TResult>
    {
        private delegate bool TryParseDelegate(string text, out TResult result);

        private static readonly TryParseDelegate _parser = (TryParseDelegate)Delegate.CreateDelegate(typeof(TryParseDelegate), typeof(TResult), "TryParse");

        public static bool TryParse(string text, out TResult result) => _parser(text, out result);
    }
}

0

Це питання "загальних обмежень". Оскільки у вас немає конкретного інтерфейсу, то ви застрягли, якщо не дотримуєтесь пропозицій попередньої відповіді.

Для документації щодо цього перевірте наступне посилання:

http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx

Він показує, як користуватися цими обмеженнями, і повинен дати вам ще декілька підказок.


0

Позичені у http://blogs.msdn.com/b/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx

при наступному посиланні: Як викликати статичний метод в C # 4.0 з динамічним типом?

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace Utils
{
   public class StaticMembersDynamicWrapper : DynamicObject
   {
      private Type _type;

      public StaticMembersDynamicWrapper(Type type) { _type = type; }

      // Handle static methods
      public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
      {
         var methods = _type
            .GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public)
            .Where(methodInfo => methodInfo.Name == binder.Name);

         var method = methods.FirstOrDefault();
         if (method != null)
         {
            result = method.Invoke(null, args);
            return true;
         }

         result = null;
         return false;
      }
   }

   public static class StaticMembersDynamicWrapperExtensions
   {
      static Dictionary<Type, DynamicObject> cache =
         new Dictionary<Type, DynamicObject>
         {
            {typeof(double), new StaticMembersDynamicWrapper(typeof(double))},
            {typeof(float), new StaticMembersDynamicWrapper(typeof(float))},
            {typeof(uint), new StaticMembersDynamicWrapper(typeof(uint))},
            {typeof(int), new StaticMembersDynamicWrapper(typeof(int))},
            {typeof(sbyte), new StaticMembersDynamicWrapper(typeof(sbyte))}
         };

      /// <summary>
      /// Allows access to static fields, properties, and methods, resolved at run-time.
      /// </summary>
      public static dynamic StaticMembers(this Type type)
      {
         DynamicObject retVal;
         if (!cache.TryGetValue(type, out retVal))
            return new StaticMembersDynamicWrapper(type);

         return retVal;
      }
   }
}

І використовуйте його наступним чином:

  public static T? ParseNumeric<T>(this string str, bool throws = true)
     where T : struct
  {
     var statics = typeof(T).StaticMembers();

     if (throws) return statics.Parse(str);

     T retval;
     if (!statics.TryParse(str, out retval)) return null;

     return retval;
  }

0

Мені вдалося отримати щось, що працює так

    var result = "44".TryParse<int>();

    Console.WriteLine( "type={0}, value={1}, valid={2}",        
    result.Value.GetType(), result.Value, result.IsValid );

Ось мій код

 public static class TryParseGeneric
    {
        //extend int
        public static dynamic TryParse<T>( this string input )
        {    
            dynamic runner = new StaticMembersDynamicWrapper( typeof( T ) );

            T value;
            bool isValid = runner.TryParse( input, out value );
            return new { IsValid = isValid, Value = value };
        }
    }


    public class StaticMembersDynamicWrapper : DynamicObject
    {
        private readonly Type _type;
        public StaticMembersDynamicWrapper( Type type ) { _type = type; }

        // Handle static properties
        public override bool TryGetMember( GetMemberBinder binder, out object result )
        {
            PropertyInfo prop = _type.GetProperty( binder.Name, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public );
            if ( prop == null )
            {
                result = null;
                return false;
            }

            result = prop.GetValue( null, null );
            return true;
        }

        // Handle static methods
        public override bool TryInvokeMember( InvokeMemberBinder binder, object [] args, out object result )
        {
            var methods = _type
            .GetMethods( BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public ).Where( methodInfo => methodInfo.Name == binder.Name );

            var method = methods.FirstOrDefault();

            if ( method == null )
            {
                result = null;

                return false;
            }

            result = method.Invoke( null, args );

            return true;
        }
    }

StaticMembersDynamicWrapper адаптований зі статті Девіда Еббо (він кидав AmbiguousMatchException)


0
public static T Get<T>(string val)
{ 
    return (T) TypeDescriptor.GetConverter(typeof (T)).ConvertFromInvariantString(val);
}

0

З TypeDescriptorвикористанням класу у TryParseвідповідний спосіб:

public static bool TryParse<T>(this string input, out T parsedValue)
{
    parsedValue = default(T);
    try
    {
        var converter = TypeDescriptor.GetConverter(typeof(T));
        parsedValue = (T)converter.ConvertFromString(input);
        return true;
    }
    catch (NotSupportedException)
    {
        return false;
    }
}

Хоча цей код може вирішити питання, включаючи пояснення, як і чому це вирішує проблему, справді допоможе покращити якість вашої публікації та, ймовірно, призведе до збільшення кількості голосів. Пам'ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз задає питання. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
подвійний звуковий сигнал

0

Використовуючи інформацію, подану вище, це те, що я розробив. Це перетворить об'єкт безпосередньо, можливо, інакше він перетворить об'єкт у рядок і викликає метод TryParse для потрібного типу об'єкта.

Я кешую методи в словнику, коли кожен стикається, щоб зменшити завантаження методу.

Можна перевірити, чи об’єкт можна безпосередньо перетворити в цільовий тип, що ще більше зменшить частину перетворення рядків. Але я покину це поки.

    /// <summary>
    /// Used to store TryParse converter methods
    /// </summary>
    private static readonly Dictionary<Type, MethodInfo> TypeConverters = new Dictionary<Type, MethodInfo>();

    /// <summary>
    /// Attempt to parse the input object to the output type
    /// </summary>
    /// <typeparam name="T">output type</typeparam>
    /// <param name="obj">input object</param>
    /// <param name="result">output result on success, default(T) on failure</param>
    /// <returns>Success</returns>
    public static bool TryParse<T>([CanBeNull] object obj, out T result)
    {
        result = default(T);

        try
        {
            switch (obj)
            {
                // don't waste time on null objects
                case null: return false;

                // if the object is already of type T, just return the value
                case T val:
                    result = val;
                    return true;
            }

            // convert the object into type T via string conversion
            var input = ((obj as string) ?? obj.ToString()).Trim();
            if (string.IsNullOrEmpty(input)) return false;

            var type = typeof (T);
            Debug.WriteLine($"Info: {nameof(TryParse)}<{type.Name}>({obj.GetType().Name}=\"{input}\")");

            if (! TypeConverters.TryGetValue(type, out var method))
            {
                // get the TryParse method for this type
                method = type.GetMethod("TryParse",
                    new[]
                    {
                        typeof (string),
                        Type.GetType($"{type.FullName}&")
                    });

                if (method is null)
                    Debug.WriteLine($"FAILED: Cannot get method for {type.Name}.TryParse()");

                // store it so we don't have to do this again
                TypeConverters.Add(type, method);
            }

            // have to keep a reference to parameters if you want to get the returned ref value
            var parameters = new object[] {input, null};
            if ((bool?) method?.Invoke(null, parameters) == true)
            {
                result = (T) parameters[1];
                return true;
            }                
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
        }

        return false;
    }

Мені довелося додати ще одну функцію для підтримки перерахувань. Здається, для розбору Enum потрібен атрибут "де T: struct", і я хочу, щоб це працювало на що-небудь конвертованому. (ймовірно, слід додати конвертований атрибут до типу). Однак деякі наступні пропозиції виглядають простішими (тим самим кращими).
Б Даффі

0

Я зібрав сюди купу ідей і закінчив дуже коротке рішення.

Це метод розширення на рядку

enter code here

Я зробив це з тими ж відбитками ніг, що і методи TryParse для числових типів

    /// <summary>
    /// string.TryParse()
    /// 
    /// This generic extension method will take a string
    ///     make sure it is not null or empty
    ///     make sure it represents some type of number e.g. "123" not "abc"
    ///     It then calls the appropriate converter for the type of T
    /// </summary>
    /// <typeparam name="T">The type of the desired retrunValue e.g. int, float, byte, decimal...</typeparam>
    /// <param name="targetText">The text to be converted</param>
    /// <param name="returnValue">a populated value of the type T or the default(T) value which is likely to be 0</param>
    /// <returns>true if the string was successfully parsed and converted otherwise false</returns>
    /// <example>
    /// float testValue = 0;
    ///  if ( "1234".TryParse<float>( out testValue ) )
    ///  {
    ///      doSomethingGood();
    ///  }
    ///  else
    ///  {
    ///      handleTheBadness();
    ///  }
    /// </example>
    public static bool TryParse<T>(this string targetText, out T returnValue )
    {
        bool returnStatus = false;

        returnValue = default(T);

        //
        // make sure the string is not null or empty and likely a number...
        // call whatever you like here or just leave it out - I would
        // at least make sure the string was not null or empty  
        //
        if ( ValidatedInputAnyWayYouLike(targetText) )
        {

            //
            // try to catch anything that blows up in the conversion process...
            //
            try
            {
                var type = typeof(T);
                var converter = TypeDescriptor.GetConverter(type);

                if (converter != null && converter.IsValid(targetText))
                {
                    returnValue = (T)converter.ConvertFromString(targetText);
                    returnStatus = true;
                }

            }
            catch
            {
                // just swallow the exception and return the default values for failure
            }

        }

        return (returnStatus);

    }

'' '


float testValue = 0; if ("1234" .TryParse <float> (out testValue)) {doSomethingGood (); } else {handleTheBadness (); }
JD Hicks

0

T.TryParse ... чому?

Я не бачу користі у виконанні такої родової TryParseфункції. Занадто багато різних стратегій для розбору та перетворення даних між різними типами з можливою конфліктною поведінкою. Як ця функція могла знати, яку стратегію обрати без контексту?

  • можна назвати класи з виділеними функціями TryParse
  • заняття з виділеними функціями Parse можна обернути результатом пробного лову та bool
  • класи з перевантаженням оператора, як би ви дозволили їм обробляти аналіз?
  • Дескриптори типів вбудовані за допомогою Convert.ChangeType. Цей API налаштовується під час виконання. Чи потрібна ваша функція поведінки за замовчуванням чи дозволяє налаштування?
  • чи варто дозволити будь-якій схемі картографування спробувати розібратися для вас?
  • як би ви вирішили конфлікти у вищесказаному?

-2

Версія для отримання нащадків з XDocument.

public static T Get<T>(XDocument xml, string descendant, T @default)
{
    try
    {
        var converter = TypeDescriptor.GetConverter(typeof (T));
        if (converter != null)
        {
            return (T) converter.ConvertFromString(xml.Descendants(descendant).Single().Value);
        }
        return @default;
    }
    catch
    {
        return @default;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.