Generisches TryParse

195

Ich versuche, eine generische Erweiterung zu erstellen, die 'TryParse' verwendet, um zu überprüfen, ob eine Zeichenfolge ein bestimmter Typ ist:

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

Dies wird nicht kompiliert, da das Symbol 'TryParse' nicht aufgelöst werden kann.

Soweit ich weiß, ist 'TryParse' nicht Teil einer Schnittstelle.

Ist das überhaupt möglich?

Aktualisieren:

Mit den folgenden Antworten habe ich Folgendes gefunden:

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

    return true;
}

Es funktioniert ganz gut, aber ich denke, Ausnahmen auf diese Weise zu verwenden, fühlt sich für mich nicht richtig an.

Update2:

Geändert, um den Typ zu übergeben, anstatt Generika zu verwenden:

public static bool Is(this string input, Type targetType)
{
    try
    {
        TypeDescriptor.GetConverter(targetType).ConvertFromString(input);
        return true;
    }
    catch
    {
        return false;
    }
}
Piers Myers
quelle
1
Ich denke, in diesem allgemeinen Fall müssen Sie sich nur mit der Ausnahme kludge befassen. Sie können Fälle hinzufügen, um nach Ints oder Doubles zu suchen, und dann die spezifischen TryParse-Methoden verwenden. Sie müssen jedoch immer noch darauf zurückgreifen, um andere Typen abzufangen.
Luke
1
Die Verwendung des Generikums ist nicht erforderlich. Übergeben Sie einfach den Typ als Parameter. public static bool Is (diese Zeichenfolgeneingabe, Typ targetType). Auf diese Weise sieht es etwas hübscher aus: x.Is (typeof (int)) -VS- x.Is <int> ()
mikesigs
2
Auf dem Konverter gibt es eine IsValid-Methode, mit der Sie überprüfen können, ob bei der Konvertierung Probleme auftreten. Ich habe die folgende Methode verwendet und scheint gut zu funktionieren. 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 Vielen Dank, dass Sie sich für diese Frage interessiert haben. Ihre Methode würde jedoch nicht ganz funktionieren, da ich überprüfen wollte, ob der Zeichenfolgenwert von einem bestimmten Typ und nicht von einem Objekt ist, obwohl Ihre Methode für Objekttypen nützlich wäre (aber würde) müssen die ConvertFrom(value)Methode in einen try-catchBlock einschließen, um die Ausnahmen zu fangen.
Piers Myers
2
Sie sollten überprüfen, ob (targetType == null), da die erste Verwendung in Ihrem Code möglicherweise auslöst, diese Ausnahme jedoch von Ihrem Fang verschluckt wird.
Nick Strupat

Antworten:

182

Sie sollten die TypeDescriptor- Klasse verwenden:

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);
    }
}
Luke
quelle
3
Entschuldigung für die Wiederbelebung, aber gibt GetConverter null zurück? Ich denke, wenn es so wäre, sollte wahrscheinlich eine Ausnahme ausgelöst werden, anstatt im Wesentlichen stillschweigend zu versagen und etwas anderes zurückzugeben. Als ich es in meiner eigenen Klasse ausprobierte (in der ich keinen Typkonverter definiert habe), bekam ich einen Konverter von GetConverter, aber dann löste der ConvertFromString eine NotSupportedException aus.
user420667
3
@ user420667, ich glaube, Sie sollten das Ergebnis von CanConvertFrom (typeof (string)) überprüfen, bevor Sie versuchen, von string zu konvertieren. Der TypeConverter unterstützt möglicherweise keine Konvertierung von Zeichenfolgen.
Reuben Bond
3
Sie können hinzufügen, wenn (typeof (T) .IsEnum) {return (T) Enum.Parse (typeof (T), input); } [als ziemlich allgemeine Verknüpfung für alle Aufzählungstypen], bevor Sie den Konverter erhalten. Ich nehme an, es hängt davon ab, wie oft Sie Enum-Typen im Gegensatz zu komplexeren Typen ausführen.
Jesse Chisholm
10
Ich verstehe nicht, warum dies als Antwort markiert und so positiv bewertet wird, wenn nicht implementiert wird, was angefordert wurde: eine generische Try Parse. Der Hauptzweck von TryParse-Methoden besteht darin, dass sie beim Versuch, die Analyse durchzuführen, keine Ausnahmen auslösen und einen viel geringeren Einfluss auf die Leistung haben, wenn die Analyse fehlschlägt und diese Lösung genau dies nicht bietet.
Florin Dumitrescu
2
Ein Problem dabei ist, dass wenn T ein int ist und die Eingabe größer als int.MaxValue ist, eine System.Exception mit System.OverFlowException als innere Ausnahme ausgelöst wird. Wenn Sie also eine OverflowException erwarten, erhalten Sie diese nur, wenn Sie die ausgelöste Exception abfragen. Der Grund ist, dass ConvertFromString eine OverflowException auslöst und die Umwandlung in T dann eine System.Exception auslöst.
Trevor
78

Ich habe kürzlich auch ein generisches TryParse benötigt. Folgendes habe ich mir ausgedacht:

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

Dann geht es einfach darum, so zu rufen:

var value = TryParse<int>("123", int.TryParse);
var value2 = TryParse<decimal>("123.123", decimal.TryParse);
Charlie Brown
quelle
3
TIch bin erst Monate später wieder auf diesen Beitrag gestoßen und habe bei der erneuten Verwendung festgestellt, dass die Methode nicht vom Handler abgeleitet werden kann, und wir müssen explizit angeben, Twann wir ihn aufrufen. Ich bin neugierig, warum kann es nicht schließen T?
Nick Strupat
25
Warum sollten Sie diese Funktion verwenden wollen? Wenn Sie wissen, welche Funktion Sie zum Parsen des Werts aufrufen müssen, rufen Sie ihn einfach direkt auf. Es kennt bereits den richtigen Eingabetyp und es sind keine Generika erforderlich. Diese Lösung würde für Typen ohne TryParseHandler nicht funktionieren.
xxbbcc
2
@xxbbcc: Ich möchte diese Funktion verwenden, da TryParse einen Booleschen Wert zurückgibt, der angibt, ob die Analyse erfolgreich war. Es gibt Ihren analysierten Wert über einen Ausgabeparameter zurück. Manchmal möchte ich einfach so etwas tun, SomeMethod(TryParse<int>(DollarTextbox.Text, int.TryParse))ohne eine Ausgabevariable zu erstellen, aus der das Ergebnis abgerufen werden kann int.TryParse. Ich stimme jedoch Nicks Meinung zu, dass die Funktion auf den Typ schließen lässt.
Walter Stabosz
1
Sehr effiziente Methode. Sehr empfehlenswert.
Vladimir Kocjancic
3
Ich würde einen Standardwert als dritten Parameter empfehlen. Dies behebt das Problem, bei dem T nicht abgeleitet werden kann. Außerdem kann man entscheiden, welchen Wert man möchte, wenn der Zeichenfolgenwert ungültig ist. Zum Beispiel könnte -1 ungültig bedeuten. public static T TryParse <T> (Zeichenfolgenwert, TryParseHandler <T> -Handler, T defaultValue)
Rhyous
33

Die Verwendung von try / catches zur Flusskontrolle ist eine schreckliche Richtlinie. Das Auslösen einer Ausnahme führt zu Leistungsverzögerungen, während die Laufzeit die Ausnahme umgeht. Überprüfen Sie stattdessen die Daten vor der Konvertierung.

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);
Denkorte
quelle
2
Ich erhalte eine Resharper-Benachrichtigung, converter != nulldie immer wahr ist, sodass sie aus dem Code entfernt werden kann.
ErikE
5
@ErikE Ich vertraue diesen ReSharper-Warnungen nicht immer. Oft können sie nicht sehen, was zur Laufzeit passiert.
ProfK
1
@ProfK MSDN sagt nicht, dass es null msdn.microsoft.com/en-us/library/ewtxwhzx.aspx zurückgeben kann
danio
@danio Ich habe gerade meine Erfahrungen mit solchen R # -Warnungen im Allgemeinen geteilt. Ich habe sicherlich nicht impliziert, dass es in diesem Fall falsch war.
ProfK
14

Wenn Sie TryParse verwenden möchten, können Sie Reflection wie folgt verwenden:

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});
}
Joseph Sturtevant
quelle
Das ist sehr cool und beseitigt die Ausnahmen, die mir sowieso nicht gefallen haben. Trotzdem ein bisschen verworren.
Piers Myers
6
Gute Lösung, aber jede Antwort, die Reflexion beinhaltet (insbesondere bei einer Dienstprogrammmethode, die leicht aus einer inneren Schleife aufgerufen werden kann), erfordert einen Haftungsausschluss bezüglich der Leistung. Siehe: stackoverflow.com/questions/25458/how-costly-is-net-reflection
Patrick M
Seufzer. Die Auswahlmöglichkeiten sind also (1) Ausnahmen für die Code-Flusssteuerung verwenden, (2) Reflexion mit ihren Geschwindigkeitskosten verwenden. Ich stimme @PiersMyers zu - keine Wahl ist ideal. Gut, dass beide arbeiten. :)
Jesse Chisholm
Ich denke , man das ersetzen kann Type.GetType(string.Format(...))mit type.MakeByRefType().
Drew Noakes
3
Die Methode muss nur einmal pro Typ und nicht einmal pro Aufruf wiedergegeben werden. Wenn Sie dies zu einer generischen Klasse mit einer statischen Elementvariablen machen, können Sie die Ausgabe der ersten Reflexion wiederverwenden.
Andrew Hill
7

Dies verwendet einen statischen Konstruktor für jeden generischen Typ, sodass er nur die teure Arbeit erledigen muss, wenn Sie ihn zum ersten Mal für einen bestimmten Typ aufrufen. Es behandelt alle Typen im System-Namespace, die über TryParse-Methoden verfügen. Es funktioniert auch mit nullbaren Versionen von jedem dieser (das sind Strukturen) mit Ausnahme von Aufzählungen.

    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;
        }
    }
Bryce Wagner
quelle
6

Wie wäre es mit so etwas?

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

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

Dies kann ziemlich einfach in eine generische Methode konvertiert werden.

 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;
}
Bob
quelle
Ist es wichtig, ob Sie vom try-Block true oder vom catch-Block false zurückgeben? Ich nehme nicht an, aber ich denke immer noch, dass es sich für mich falsch anfühlt, Ausnahmen auf diese Weise zu verwenden ...
Piers Myers
3
Es spielt keine Rolle, ob Sie vom catch-Block zurückkehren, dies ist das gleiche. Übrigens. Normalerweise ist es schlecht, eine generische catch-Klausel zu haben : catch { }. In diesem Fall gibt es jedoch keine Alternative, da .NET im Falle eines Konvertierungsfehlers BaseNumberConverterdie ExceptionBasisklasse auslöst. Das ist sehr unglücklich. Tatsächlich gibt es noch einige Stellen, an denen dieser Basistyp geworfen wird. Hoffentlich wird Microsoft diese in einer zukünftigen Version des Frameworks beheben.
Steven
Danke Steven, hätte es nicht besser sagen können.
Bob
Das Ergebnis der Konvertierung wird nicht verwendet: Code ist redundant.
BillW
4

Sie können es nicht für allgemeine Typen tun.

Sie können eine Schnittstelle ITryParsable erstellen und für benutzerdefinierte Typen verwenden, die diese Schnittstelle implementieren.

Ich denke jedoch, dass Sie beabsichtigen, dies mit Grundtypen wie intund zu verwenden DateTime. Sie können diese Typen nicht ändern, um neue Schnittstellen zu implementieren.

Mark Byers
quelle
1
Ich frage mich, ob das mit dem dynamischen Schlüsselwort in .net 4 funktionieren würde.
Pierre-Alain Vigeant
@Pierre: Dies funktioniert in C # mit dem dynamicSchlüsselwort standardmäßig nicht , da es bei statischer Typisierung nicht funktioniert. Sie können Ihr eigenes dynamisches Objekt erstellen, das dies verarbeiten kann, dies ist jedoch nicht die Standardeinstellung.
Steven
4

Inspiriert von der hier von Charlie Brown veröffentlichten Lösung habe ich mithilfe von Reflection ein generisches TryParse erstellt, das optional den analysierten Wert ausgibt:

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

Es kann so genannt werden:

string input = "123";
decimal myDecimal;

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

Update:
Auch dank der Lösung von YotaXP, die mir sehr gefällt, habe ich eine Version erstellt, die keine Erweiterungsmethoden verwendet, aber dennoch einen Singleton hat, wodurch die Notwendigkeit einer Reflexion minimiert wird:

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

Nennen Sie es so:

string input = "987";
decimal myDecimal;

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

Ziemlich spät zur Party, aber hier ist, was ich mir ausgedacht habe. Keine Ausnahmen, einmalige (pro Typ) Reflexion.

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

Die zusätzliche Klasse ist erforderlich, da Erweiterungsmethoden in generischen Klassen nicht zulässig sind. Dies ermöglicht eine einfache Verwendung, wie unten gezeigt, und trifft die Reflexion nur bei der ersten Verwendung eines Typs.

"5643".ParseAs<int>()
YotaXP
quelle
3

Hier ist eine weitere Option.

Ich habe eine Klasse geschrieben, die es einfach macht, eine beliebige Anzahl von TryParseHandlern zu registrieren . Damit kann ich Folgendes tun:

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

Ich werde 42auf die Konsole gedruckt.

Die Klasse ist:

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);
    }
}
Rätselhaftigkeit
quelle
Ich mag das, aber wie würden Sie es ohne Generika tun. Anwendungsfall ist natürlich Reflexion.
Sinaesthetic
Ich habe eine überladene Methode hinzugefügt, die Reflexions-Hackery ausführt. Wenn es einen eleganteren Weg gibt, es anzugehen, bin ich alle Augen lol gist.github.com/dasjestyr/90d8ef4dea179a6e08ddd85e0dacbc94
Sinaesthetic
2

Wenn ich fast genau das tun wollte, musste ich es auf die harte Tour umsetzen, wenn ich darüber nachdachte. Geben Sie eine oder Methode Tan typeof(T)und suchen Sie sie, wenn Sie sie gefunden haben.TryParseParse

JSB ձոգչ
quelle
Das wollte ich vorschlagen.
Steven Evers
2

Das ist mein Versuch. Ich habe es als "Übung" gemacht. Ich habe versucht, es so ähnlich wie die vorhandenen " Convert.ToX () " -ones usw. zu machen. Aber dies ist eine Erweiterungsmethode:

    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; }
    }
W0lfw00ds
quelle
Der Hauptnachteil dieser Vergleich TypeConverter.ConvertFrom()ist , dass die Source - Klasse die Typumwandlung zur Verfügung stellen muss, die in der Regel bedeutet , dass Sie nicht Umstellung auf benutzerdefinierte Typen unterstützen kann.
Ian Goldby
1

Wie Sie sagten, TryParseist nicht Teil einer Schnittstelle. Es ist auch kein Mitglied einer bestimmten Basisklasse, da es tatsächlich ist staticund staticFunktionen nicht sein können virtual. Der Compiler kann also nicht sicherstellen, dass Ttatsächlich ein Mitglied aufgerufen TryParsewird. Dies funktioniert also nicht.

Wie @Mark sagte, können Sie Ihre eigene Benutzeroberfläche erstellen und benutzerdefinierte Typen verwenden, aber Sie haben kein Glück mit den integrierten Typen.

Donnie
quelle
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);
    }
}
ChaosPandion
quelle
0

Dies ist eine Frage der "allgemeinen Einschränkungen". Da Sie keine bestimmte Benutzeroberfläche haben, stecken Sie fest, es sei denn, Sie folgen den Vorschlägen der vorherigen Antwort.

Eine Dokumentation hierzu finden Sie unter folgendem Link:

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

Es zeigt Ihnen, wie Sie diese Einschränkungen verwenden, und sollte Ihnen weitere Hinweise geben.

Simon
quelle
0

Ausgeliehen von http://blogs.msdn.com/b/davidebb/archive/2009/10/23/using-c-dynamic-to-call-static-members.aspx

Wenn Sie dieser Referenz folgen: Wie rufe ich eine statische Methode in C # 4.0 mit dynamischem Typ auf?

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

Und verwenden Sie es wie folgt:

  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;
  }
GregC
quelle
0

Ich habe es geschafft, etwas zu bekommen, das so funktioniert

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

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

Hier ist mein Code

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

Der StaticMembersDynamicWrapper wurde aus dem Artikel von David Ebbo übernommen (er hat eine AmbiguousMatchException ausgelöst ).


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

Mit TypeDescriptorKlassenverwendung in TryParseverwandter Weise:

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;
    }
}
Vova
quelle
Während dieser Code die Frage lösen kann, einschließlich einer Erklärung, wie und warum dies das Problem löst, würde dies wirklich dazu beitragen, die Qualität Ihres Beitrags zu verbessern, und wahrscheinlich zu mehr Up-Votes führen. Denken Sie daran, dass Sie in Zukunft die Frage für die Leser beantworten, nicht nur für die Person, die jetzt fragt. Bitte bearbeiten Sie Ihre Antwort, um Erklärungen hinzuzufügen und anzugeben, welche Einschränkungen und Annahmen gelten.
Doppel-Piepton
0

Mit den obigen Informationen habe ich Folgendes entwickelt. Es wird das Objekt direkt konvertieren ist möglich, andernfalls wird das Objekt in eine Zeichenfolge konvertiert und die TryParse-Methode für den gewünschten Objekttyp aufgerufen.

Ich speichere die Methoden in einem Wörterbuch zwischen, wobei jede gefunden wird, um die Last beim Abrufen von Methoden zu verringern.

Es ist möglich zu testen, ob das Objekt direkt in den Zieltyp konvertiert werden kann, wodurch der Teil der Zeichenfolgenkonvertierung weiter reduziert würde. Aber das werde ich vorerst weglassen.

    /// <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;
    }
B Duffy
quelle
Ich musste eine weitere Funktion hinzufügen, um Aufzählungen zu unterstützen. Das Parsen von Enum erfordert anscheinend das Attribut "where T: struct", und ich möchte, dass dies bei allen konvertierbaren Elementen funktioniert. (sollte dem Typ wahrscheinlich ein konvertierbares Attribut hinzufügen). Einige der folgenden Vorschläge sehen jedoch einfacher (also besser) aus.
B Duffy
0

Ich habe hier eine Reihe von Ideen zusammengestellt und eine sehr kurze Lösung gefunden.

Dies ist eine Erweiterungsmethode für eine Zeichenfolge

enter code here

Ich habe es mit dem gleichen Footprint wie die TryParse-Methoden für die numerischen Typen erstellt

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

    }

'' '

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

T.TryParse ... warum?

Ich sehe keinen Vorteil in einer solchen generischen TryParseFunktion. Es gibt zu viele verschiedene Strategien zum Parsen und Konvertieren von Daten zwischen verschiedenen Typen mit möglicherweise widersprüchlichem Verhalten. Wie kann diese Funktion wissen, welche Strategie kontextfrei gewählt werden soll?

  • Klassen mit dedizierten TryParse-Funktionen könnten aufgerufen werden
  • Klassen mit dedizierten Parse-Funktionen können mit Try-Catch- und Bool-Ergebnissen umbrochen werden
  • Klassen mit Operatorüberladungen, wie würden Sie sie das Parsen handhaben lassen?
  • Typdeskriptoren werden mithilfe von integriert Convert.ChangeType. Diese API kann zur Laufzeit angepasst werden. Erfordert Ihre Funktion ein Standardverhalten oder ermöglicht sie eine Anpassung?
  • Sollten Sie zulassen, dass ein Mapping-Framework versucht, für Sie zu analysieren?
  • Wie würden Sie mit den oben genannten Konflikten umgehen?

quelle
-2

Eine Version zum Abrufen von Nachkommen von 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;
    }
}
Andreas
quelle