Ogólny TryParse


196

Próbuję utworzyć ogólne rozszerzenie, które używa „TryParse”, aby sprawdzić, czy łańcuch jest danego typu:

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

to się nie skompiluje, ponieważ nie może rozwiązać symbolu „TryParse”

Jak rozumiem, „TryParse” nie jest częścią żadnego interfejsu.

Czy to w ogóle można zrobić?

Aktualizacja:

Korzystając z poniższych odpowiedzi, wymyśliłem:

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

    return true;
}

Działa całkiem dobrze, ale myślę, że stosowanie wyjątków w ten sposób nie wydaje mi się właściwe.

Aktualizacja 2:

Zmodyfikowano tak, aby podawał typ, zamiast używać ogólnych:

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

1
myślę, że w tym ogólnym przypadku będziesz musiał poradzić sobie z wyjątkiem kludge. możesz dodać przypadki, aby sprawdzić takie rzeczy, jak ints lub double, a następnie użyć określonych metod TryParse, ale nadal będziesz musiał polegać na tym, aby złapać inne typy.
luke

1
Korzystanie z ogólnych nie jest konieczne. Po prostu podaj Typ jako parametr. public static bool Is (ten ciąg wejściowy, wpisz targetType). W ten sposób wywołanie wygląda trochę ładniej: x.Is (typeof (int)) -VS- x.Is <int> ()
mikesigs

2
W konwertorze znajduje się metoda IsValid, która pozwala sprawdzić, czy konwersja spowoduje problemy. Użyłem poniższej metody i wydaje się, że działa dobrze. 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 Dziękujemy za zainteresowanie tym pytaniem, jednak twoja metoda nie działałaby, ponieważ chciałem sprawdzić, czy wartość ciągu jest określonego typu, a nie obiektu, chociaż twoja metoda byłaby przydatna dla typów obiektów (ale muszą zawinąć ConvertFrom(value)metodę w try-catchblok, aby uchwycić wyjątki
Piers Myers,

2
Powinieneś sprawdzić, czy (targetType == null), ponieważ pierwsze użycie go w kodzie może wyrzucić, ale ten wyjątek zostałby połknięty przez twój haczyk.
Nick Strupat

Odpowiedzi:


183

Powinieneś użyć klasy 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
Przepraszam za wskrzeszenie, ale czy GetConverter zwraca wartość null? Myślę, że gdyby tak się stało, to prawdopodobnie powinien zostać zgłoszony wyjątek zamiast zasadniczo po cichu zawieść i zwrócić coś innego. Kiedy wypróbowałem to na własnej klasie (w której nie zdefiniowałem konwertera typów), dostałem konwerter z GetConverter, ale następnie ConvertFromString zgłosił wyjątek NotSupportedException.
user420667

3
@ user420667, uważam, że powinieneś sprawdzić wynik CanConvertFrom (typeof (string)) przed próbą konwersji z ciągu. TypeConverter może nie obsługiwać konwersji z łańcucha.
Reuben Bond

3
Możesz dodać if (typeof (T) .IsEnum) {return (T) Enum.Parse (typeof (T), input); } [jako dość ogólny skrót dla wszystkich typów Enum] przed uzyskaniem Konwertera. Podejrzewam, że zależy to od tego, jak często będziesz robił typy Enum w przeciwieństwie do bardziej złożonych typów.
Jesse Chisholm,

10
Nie rozumiem, dlaczego tak jest oznaczony jako odpowiedź i upvoted tyle, jeśli nie realizuje to, co było wymagane: ogólny Try przetworzenia. Głównym celem metod TryParse jest to, że nie rzucają wyjątków podczas próby parsowania i mają znacznie mniejszy wpływ na wydajność, gdy parsowanie się nie powiedzie, a to rozwiązanie tego nie zapewnia.
Florin Dumitrescu

2
Jednym z problemów jest to, że jeśli T jest liczbą całkowitą, a dane wejściowe są większe niż int.MaxValue, zgłosi wyjątek System.Exception w / System.OverFlowException jako wyjątek wewnętrzny. Jeśli więc spodziewasz się wyjątku OverflowException, nie dostaniesz go, dopóki nie przesłuchasz zgłoszonego wyjątku. Powodem jest to, że ConvertFromString zgłasza OverflowException, a następnie rzutowanie na T zgłasza System.Exception.
Trevor,

78

Ostatnio również wymagałem ogólnego tryParse. Oto, co wymyśliłem;

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

Zatem wystarczy po prostu zadzwonić w ten sposób:

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

3
Ponownie natknąłem się na ten post ponownie kilka miesięcy później i zauważyłem, używając go ponownie, że metoda nie może wywnioskować Tz modułu obsługi i musimy wyraźnie określić, Tkiedy go wywołujemy. Jestem ciekawy, dlaczego to nie może wywnioskować T?
Nick Strupat,

25
Dlaczego chcesz korzystać z tej funkcji? Jeśli wiesz, którą funkcję wywołać, aby przeanalizować wartość, dlaczego po prostu nie wywołać jej bezpośrednio? Zna już odpowiedni typ danych wejściowych i nie ma potrzeby stosowania generycznych. To rozwiązanie nie działałoby dla typów bez TryParseHandler.
xxbbcc

2
@xxbbcc: Chciałbym użyć tej funkcji, ponieważ TryParse zwraca wartość logiczną wskazującą, czy parsowanie zakończyło się powodzeniem. Zwraca przeanalizowaną wartość za pomocą parametru wyjściowego. Czasami chcę po prostu zrobić coś takiego SomeMethod(TryParse<int>(DollarTextbox.Text, int.TryParse))bez tworzenia zmiennej wyjściowej do przechwytywania wyniku int.TryParse. Zgadzam się jednak z sentymentem Nicka, że ​​funkcja wnioskuje o typie.
Walter Stabosz,

1
Bardzo skuteczna metoda. Wysoce rekomendowane.
Vladimir Kocjancic

3
Poleciłbym wartość domyślną jako trzeci parametr. To rozwiązuje problem polegający na tym, że T nie można wywnioskować. Pozwala także zdecydować, jakiej wartości chcą, jeśli wartość ciągu jest niepoprawna. Na przykład -1 może oznaczać nieprawidłowy. public static T TryParse <T> (wartość ciągu, moduł obsługi TryParseHandler <T>, T defaultValue)
Rhyous 30.09.16

33

Używanie try / catch do kontroli przepływu jest okropną polityką. Zgłoszenie wyjątku powoduje opóźnienia w wydajności podczas działania środowiska wykonawczego wokół tego wyjątku. Zamiast tego sprawdź poprawność danych przed konwersją.

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
Otrzymuję powiadomienie Resharper, które converter != nulljest zawsze prawdziwe, więc można je usunąć z kodu.
ErikE

5
@ErikE Nie zawsze ufam ostrzeżeniom ReSharper. Często nie widzą, co dzieje się w czasie wykonywania.
ProfK

1
@ProfK MSDN nie mówi, że może zwrócić wartość null msdn.microsoft.com/en-us/library/ewtxwhzx.aspx
danio

@danio Właśnie dzieliłem się swoim doświadczeniem z takimi ostrzeżeniami R # w ogóle. Z pewnością nie sugerowałem, że w tym przypadku było źle.
ProfK

14

Jeśli korzystasz z TryParse, możesz użyć odbicia i zrobić to w następujący sposób:

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

To bardzo fajne i pozbywa się wyjątków, których i tak nie lubiłem. Wciąż trochę skomplikowane.
Piers Myers

6
Dobre rozwiązanie, ale każda odpowiedź dotycząca refleksji (szczególnie w metodzie użytecznej, którą można łatwo wywołać z wewnętrznej pętli) wymaga wyłączenia odpowiedzialności dotyczącej wydajności. Zobacz: stackoverflow.com/questions/25458/how-costly-is-net-reflection
Patrick M

Westchnienie. Do wyboru są (1) użycie wyjątków do kontroli przepływu kodu, (2) użycie odbicia z jego kosztami prędkości. Zgadzam się z @PiersMyers - żaden wybór nie jest idealny. Dobrze, że oboje pracują. :)
Jesse Chisholm,

Myślę, że można zastąpić Type.GetType(string.Format(...))z type.MakeByRefType().
Drew Noakes,

3
metoda musi być odzwierciedlona tylko raz dla każdego typu, a nie raz dla każdego połączenia. jeśli uczynisz to klasą ogólną ze statyczną zmienną składową, możesz ponownie użyć wyniku pierwszego odbicia.
Andrew Hill

7

Używa konstruktora statycznego dla każdego typu ogólnego, więc musi wykonać kosztowną pracę tylko przy pierwszym wywołaniu danego typu. Obsługuje wszystkie typy w przestrzeni nazw systemu, które mają metody TryParse. Działa również z zerowymi wersjami każdej z nich (które są strukturami), z wyjątkiem wyliczeń.

    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

Co powiesz na coś takiego?

http://madskristensen.net/post/Universal-data-type-checker.aspx ( Archiwum )

/// <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;
  }

Można to łatwo przekonwertować na metodę ogólną.

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

Czy ma znaczenie, czy zwracasz wartość true z bloku try, czy zwracasz wartość false z bloku catch? Chyba nie, ale nadal uważam, że stosowanie wyjątków w ten sposób wydaje mi się złe ...
Piers Myers

3
Nie ma znaczenia, czy wrócisz z bloku catch, to samo. btw. Zazwyczaj jest to złe mieć ogólną klauzulę catch: catch { }. Jednak w tym przypadku nie ma alternatywy, ponieważ .NET BaseNumberConverterrzuca Exceptionklasę bazową w przypadku błędu konwersji. To bardzo niefortunne. W rzeczywistości jest jeszcze kilka miejsc, w których rzucono ten podstawowy typ. Mamy nadzieję, że Microsoft naprawi je w przyszłej wersji frameworka.
Steven

Dzięki Steven, nie mógłbym tego lepiej powiedzieć.
Bob

Wynik konwersji nie jest wykorzystywany: kod jest zbędny.
BillW

4

Nie możesz tego zrobić na typach ogólnych.

Co możesz zrobić, to utworzyć interfejs ITryParsable i użyć go do niestandardowych typów, które implementują ten interfejs.

Myślę jednak, że zamierzasz używać tego z podstawowymi typami, takimi jak inti DateTime. Nie można zmienić tych typów w celu wdrożenia nowych interfejsów.


1
Zastanawiam się, czy to zadziałałoby przy użyciu słowa kluczowego dynamicznego w .net 4?
Pierre-Alain Vigeant,

@Pierre: To nie będzie domyślnie działać w C # ze dynamicsłowem kluczowym, ponieważ nie będzie działać na pisaniu statycznym. Możesz utworzyć własny obiekt dynamiczny, który może to obsłużyć, ale nie jest to ustawienie domyślne.
Steven

4

Zainspirowany rozwiązaniem opublikowanym tutaj przez Charliego Browna, stworzyłem ogólny TryParse za pomocą odbicia, które opcjonalnie wypisuje przeanalizowaną wartość:

/// <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;
}

Można to nazwać w ten sposób:

string input = "123";
decimal myDecimal;

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

Aktualizacja:
Również dzięki rozwiązaniu YotaXP, które bardzo mi się podoba, stworzyłem wersję, która nie korzysta z metod rozszerzeń, ale wciąż ma singleton, co minimalizuje potrzebę refleksji:

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

Nazwij to tak:

string input = "987";
decimal myDecimal;

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

3

Trochę późno na imprezę, ale oto co wymyśliłem. Bez wyjątków, jednorazowe odbicie (według typu).

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

Wymagana jest dodatkowa klasa, ponieważ metody rozszerzenia nie są dozwolone w klasach ogólnych. Umożliwia to proste użycie, jak pokazano poniżej, i odbija odbicie tylko przy pierwszym użyciu typu.

"5643".ParseAs<int>()

3

Oto kolejna opcja.

Napisałem klasę, która ułatwia zarejestrowanie dowolnej liczby programów TryParseobsługi. Pozwala mi to zrobić:

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

Zostaję 42wydrukowany na konsoli.

Klasa jest:

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

Lubię to, ale jak byś to zrobił bez leków generycznych. Oczywiście użyj przypadku będącego odzwierciedleniem.
Sinaesthetic

Dodałem przeciążoną metodę, która powoduje hackery refleksyjne. Jeśli istnieje bardziej elegancki sposób rozwiązania tego problemu, jestem wszystkim oczom lol gist.github.com/dasjestyr/90d8ef4dea179a6e08ddd85e0dacbc94
Sinaesthetic

2

Kiedy chciałem zrobić dokładnie to dokładnie, musiałem to zrealizować w sposób trudny, biorąc pod uwagę refleksję. Biorąc pod uwagę T, zastanów się typeof(T)i poszukaj metody TryParselub Parsemetody, wywołując ją, jeśli ją znalazłeś.


To właśnie zamierzałem zasugerować.
Steven Evers

2

To jest moja próba. Zrobiłem to jako „ćwiczenie”. Próbowałem uczynić to tak podobnym do użycia jak istniejące „ Convert.ToX () ” itp. Ale ta metoda jest metodą rozszerzenia:

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

Główną wadą tego w porównaniu z TypeConverter.ConvertFrom()tym jest to, że klasa źródłowa musi zapewniać konwersję typu, co ogólnie oznacza, że ​​nie można obsługiwać konwersji na typy niestandardowe.
Ian Goldby,

1

Jak powiedziałeś, TryParsenie jest częścią interfejsu. To również nie jest członkiem danej klasy bazowej, ponieważ jest to faktycznie statici staticfunkcje nie mogą być virtual. Tak więc kompilator nie ma możliwości zapewnienia, że Tfaktycznie ma on członka o nazwie TryParse, więc to nie działa.

Jak powiedział @Mark, możesz stworzyć własny interfejs i używać niestandardowych typów, ale nie masz szczęścia do wbudowanych typów.


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

Jest to kwestia „ogólnych ograniczeń”. Ponieważ nie masz określonego interfejsu, utkniesz, chyba że podążasz za sugestiami z poprzedniej odpowiedzi.

Aby uzyskać dokumentację na ten temat, sprawdź następujący link:

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

Pokazuje, jak korzystać z tych ograniczeń i powinien dać ci więcej wskazówek.


0

Pożyczone od http://blogs.msdn.com/b/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx

postępując zgodnie z tym odniesieniem: Jak wywołać metodę statyczną w C # 4.0 z typem dynamicznym?

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

I użyj go w następujący sposób:

  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

Udało mi się zdobyć coś, co tak działa

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

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

Oto mój kod

 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 został zaadaptowany z artykułu Davida Ebbo ( zgłaszał wyjątek AmbiguousMatchException )


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

0

Z TypeDescriptorwykorzystaniem klasy w TryParsepodobny sposób:

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

Chociaż ten kod może rozwiązać pytanie, w tym wyjaśnienie, w jaki sposób i dlaczego to rozwiązuje problem, naprawdę pomógłby poprawić jakość twojego postu i prawdopodobnie doprowadziłby do większej liczby głosów. Pamiętaj, że odpowiadasz na pytanie czytelników w przyszłości, a nie tylko osoby zadającej teraz pytanie. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnień i dać wskazówkę co zastosować ograniczenia i założenia.
podwójny dźwięk

0

Korzystając z powyższych informacji, opracowałem to. Przekształci obiekt bezpośrednio, jest to możliwe, w przeciwnym razie przekształci obiekt w ciąg i wywoła metodę TryParse dla pożądanego typu obiektu.

Metody buforuję w słowniku, gdy każda z nich jest napotkana, aby zmniejszyć obciążenie pobierania metody.

Możliwe jest sprawdzenie, czy obiekt można bezpośrednio przekonwertować na typ docelowy, co dodatkowo zmniejszy część konwersji ciągu. Ale na razie to pominę.

    /// <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;
    }

Musiałem dodać inną funkcję do obsługi wyliczeń. Wydaje się, że analizowanie Enum wymaga atrybutu „where T: struct” i chcę, aby działało to na dowolnym programie konwertowalnym. (prawdopodobnie powinien dodać atrybut konwertowalny do typu). Jednak niektóre z poniższych sugestii wyglądają na prostsze (a więc lepsze).
B Duffy,

0

Zebrałem tu kilka pomysłów i otrzymałem bardzo krótkie rozwiązanie.

Jest to metoda rozszerzenia ciągu

enter code here

Zrobiłem to z takim samym drukiem stóp jak metody TryParse na typach numerycznych

    /// <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 ... dlaczego?

Nie widzę żadnej korzyści z takiej ogólnej TryParsefunkcji. Istnieje zbyt wiele różnych strategii analizowania i konwertowania danych między różnymi typami, z możliwym konfliktowym zachowaniem. Skąd ta funkcja mogła wiedzieć, którą wybrać strategię bez kontekstu?

  • można wywoływać klasy z dedykowanymi funkcjami TryParse
  • klasy z dedykowanymi funkcjami Parse można łączyć z wynikiem try-catch i bool
  • klasy z przeciążeniem operatora, jak pozwoliłbyś im obsłużyć parsowanie?
  • deskryptory typów są wbudowane przy użyciu Convert.ChangeType. Ten interfejs API można dostosować w czasie wykonywania. Czy twoja funkcja wymaga domyślnego zachowania lub pozwala na dostosowanie?
  • czy powinieneś pozwolić, aby jakakolwiek platforma mapująca spróbowała parsować dla ciebie?
  • jak poradzilibyście sobie z powyższymi konfliktami?

-2

Wersja do pobierania potomków z 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;
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.