Generic TryParse


196

J'essaie de créer une extension générique qui utilise «TryParse» pour vérifier si une chaîne est d'un type donné:

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

cela ne se compilera pas car il ne peut pas résoudre le symbole 'TryParse'

Si je comprends bien, «TryParse» ne fait partie d'aucune interface.

Est-ce possible de faire du tout?

Mettre à jour:

En utilisant les réponses ci-dessous, j'ai trouvé:

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

    return true;
}

Cela fonctionne assez bien, mais je pense que l'utilisation d'exceptions de cette manière ne me semble pas appropriée.

Update2:

Modifié pour passer le type plutôt que d'utiliser des génériques:

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

1
je pense que dans ce cas général, vous aurez juste à faire face à l'exception kludge. vous pouvez ajouter des cas pour vérifier des choses comme des entiers ou des doubles, puis utiliser les méthodes TryParse spécifiques, mais vous devrez toujours vous en remettre pour attraper d'autres types.
luke

1
L'utilisation du générique n'est pas nécessaire. Passez simplement le Type comme paramètre. public static bool Is (cette entrée de chaîne, Type targetType). De cette façon, l'appeler semble un peu plus joli: x.Is (typeof (int)) -VS- x.Is <int> ()
mikesigs

2
Il existe une méthode IsValid sur le convertisseur pour vous permettre de vérifier si la conversion aura des problèmes. J'ai utilisé la méthode ci-dessous et semble bien fonctionner. 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 Merci d'avoir montré un intérêt pour cette question, mais votre méthode ne fonctionnerait pas tout à fait car je voulais vérifier si la valeur de la chaîne était d'un certain type plutôt qu'un objet, bien que votre méthode soit utile pour les types d'objet (mais doivent encapsuler la ConvertFrom(value)méthode dans un try-catchbloc pour intercepter les exceptions.
Piers Myers

2
Vous devriez vérifier si (targetType == null) car la première utilisation de celui-ci dans votre code peut être levée, mais cette exception serait avalée par votre capture.
Nick Strupat

Réponses:


183

Vous devez utiliser la classe 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
Désolé de ressusciter, mais GetConverter renvoie-t-il null? Je pense que si c'était le cas, alors probablement une exception devrait être levée au lieu d'échouer en silence et renvoyer autre chose. Lorsque je l'ai essayé sur ma propre classe (dans laquelle je n'ai pas défini de convertisseur de type), j'ai obtenu un convertisseur de GetConverter, mais ensuite ConvertFromString a lancé une exception NotSupportedException.
user420667

3
@ user420667, je pense que vous devriez vérifier le résultat de CanConvertFrom (typeof (string)) avant d'essayer de convertir à partir de string. Le TypeConverter peut ne pas prendre en charge la conversion à partir d'une chaîne.
Reuben Bond

3
Vous pouvez ajouter if (typeof (T) .IsEnum) {return (T) Enum.Parse (typeof (T), input); } [comme raccourci assez générique pour tous les types Enum] avant d'obtenir le convertisseur. Je suppose que cela dépend de la fréquence à laquelle vous ferez des types Enum par opposition à des types plus complexes.
Jesse Chisholm

10
Je ne comprends pas pourquoi cela est marqué comme réponse et tant voté quand il n'implémente pas ce qui était demandé: un Try Parse générique . L'objectif principal des méthodes TryParse est qu'elles ne génèrent pas d'exceptions lors de la tentative d'exécution de l'analyse et qu'elles ont un impact beaucoup plus faible sur les performances lorsque l'analyse échoue et que cette solution ne fournit pas exactement cela.
Florin Dumitrescu

2
Un problème avec ceci est que si T est un int et que l'entrée est plus grande que int.MaxValue, il lèvera une System.Exception w / System.OverFlowException comme exception interne. Donc, si vous attendez une OverflowException, vous ne l'obtiendrez que si vous interrogez l'exception levée. La raison en est que ConvertFromString lève une OverflowException, puis le transtypage en T lève une System.Exception.
Trevor

78

J'ai également récemment eu besoin d'un TryParse générique. Voici ce que j'ai trouvé;

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

Il suffit alors d'appeler ainsi:

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

3
Je suis juste tombé sur ce message à nouveau des mois plus tard et Tj'ai remarqué en l'utilisant à nouveau que la méthode ne pouvait pas déduire du gestionnaire, et nous devons spécifier explicitement Tquand nous l'appelons. Je suis curieux, pourquoi ne peut-il pas en déduire T?
Nick Strupat

25
Pourquoi voudriez-vous utiliser cette fonction? Si vous savez quelle fonction appeler pour analyser la valeur, pourquoi ne pas l'appeler directement? Il connaît déjà le bon type d'entrée et n'a pas besoin de génériques. Cette solution ne fonctionnerait pas pour les types sans TryParseHandler.
xxbbcc

2
@xxbbcc: Je voudrais utiliser cette fonction car TryParse renvoie un booléen qui indique si l'analyse a réussi. Il renvoie votre valeur analysée via un paramètre de sortie. Parfois, je veux juste faire quelque chose comme ça SomeMethod(TryParse<int>(DollarTextbox.Text, int.TryParse))sans créer une variable de sortie pour attraper le résultat int.TryParse. Cependant, je suis d'accord avec le sentiment de Nick d'avoir la fonction inférer le type.
Walter Stabosz

1
Méthode très efficace. Hautement recommandé.
Vladimir Kocjancic

3
Je recommanderais une valeur par défaut comme troisième paramètre. Cela corrige le problème où T ne peut pas être déduit. En outre, cela permet à l'un de décider quelle valeur il souhaite si la valeur de chaîne n'est pas valide. Par exemple, -1 peut signifier non valide. statique public T TryParse <T> (valeur de chaîne, gestionnaire TryParseHandler <T>, T defaultValue)
Rhyous

33

L'utilisation de try / catch pour le contrôle de flux est une politique terrible. La levée d'une exception entraîne des retards de performances pendant que le runtime contourne l'exception. Validez plutôt les données avant de les convertir.

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
Je reçois un avis Resharper qui converter != nullest toujours vrai, donc il peut être supprimé du code.
ErikE

5
@ErikE Je ne fais pas toujours confiance à ces avertissements ReSharper. Souvent, ils ne peuvent pas voir ce qui se passe lors de l'exécution.
ProfK

1
@ProfK MSDN ne dit pas qu'il peut retourner null msdn.microsoft.com/en-us/library/ewtxwhzx.aspx
danio

@danio Je partageais simplement mon expérience avec de tels avertissements R # en général. Je n'ai certainement pas laissé entendre que c'était mal dans ce cas.
ProfK

14

Si vous êtes prêt à utiliser TryParse, vous pouvez utiliser la réflexion et le faire comme ceci:

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

C'est très cool, et cela élimine les exceptions que je n'aimais pas de toute façon. Encore un peu alambiqué cependant.
Piers Myers

6
Belle solution, mais toute réponse impliquant une réflexion (en particulier dans une méthode utilitaire qui pourrait facilement être appelée à partir d'une boucle interne) nécessite un avertissement sur les performances. Voir: stackoverflow.com/questions/25458/how-costly-is-net-reflection
Patrick M

Soupir. Ainsi, les choix sont (1) utiliser des exceptions pour le contrôle du flux de code, (2) utiliser la réflexion avec ses coûts de vitesse. Je suis d'accord avec @PiersMyers - aucun des deux choix n'est idéal. Heureusement qu'ils fonctionnent tous les deux. :)
Jesse Chisholm

Je pense que vous pouvez remplacer le Type.GetType(string.Format(...))par type.MakeByRefType().
Drew Noakes

3
la méthode ne doit être reflétée qu'une fois par type, pas une fois par appel. si vous en faites une classe générique avec une variable membre statique, vous pouvez réutiliser la sortie de la première réflexion.
Andrew Hill

7

Celui-ci utilise un constructeur statique pour chaque type générique, il n'a donc qu'à effectuer le travail coûteux la première fois que vous l'appelez sur un type donné. Il gère tous les types de l'espace de noms système qui ont des méthodes TryParse. Il fonctionne également avec les versions nullables de chacun de ceux-ci (qui sont des structures) à l'exception des énumérations.

    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

Que diriez-vous quelque chose comme ça?

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

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

Cela peut être converti assez facilement en une méthode générique.

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

Est-il important que vous retourniez vrai du bloc try ou faux du bloc catch? Je suppose que non, mais je pense toujours que l'utilisation d'exceptions de cette manière me fait du tort ...
Piers Myers

3
Peu importe que vous reveniez du bloc catch, c'est la même chose. btw. Habituellement , il est mauvais d'avoir une clause catch générique: catch { }. Cependant, dans ce cas, il n'y a pas d'alternative, car le .NET BaseNumberConverterlève la Exceptionclasse de base en cas d'erreur de conversion. C'est très malheureux. En fait, il y a encore pas mal d'endroits où ce type de base est lancé. Espérons que Microsoft les corrigera dans une future version du framework.
Steven

Merci Steven, n'aurait pas pu mieux dire.
Bob

Pas d'utilisation du résultat de la conversion: le code est redondant.
BillW

4

Vous ne pouvez pas le faire sur des types généraux.

Ce que vous pourriez faire est de créer une interface ITryParsable et de l'utiliser pour des types personnalisés qui implémentent cette interface.

Je suppose cependant que vous avez l'intention de l'utiliser avec des types de base comme intet DateTime. Vous ne pouvez pas modifier ces types pour implémenter de nouvelles interfaces.


1
Je me demande si cela fonctionnerait en utilisant le mot-clé dynamique dans .net 4?
Pierre-Alain Vigeant

@Pierre: Cela ne fonctionnera pas par défaut en C # avec le dynamicmot - clé, car cela ne fonctionnera pas sur la frappe statique. Vous pouvez créer votre propre objet dynamique qui peut gérer cela, mais ce n'est pas par défaut.
Steven

4

Inspiré par la solution publiée ici par Charlie Brown, j'ai créé un TryParse générique à l'aide de la réflexion qui génère éventuellement la valeur analysée:

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

On peut l'appeler ainsi:

string input = "123";
decimal myDecimal;

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

Mise à jour:
également grâce à la solution de YotaXP que j'aime beaucoup, j'ai créé une version qui n'utilise pas de méthodes d'extension mais qui a toujours un singleton, minimisant le besoin de faire de la réflexion:

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

Appelez ça comme ceci:

string input = "987";
decimal myDecimal;

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

3

Un peu tard pour la fête, mais voici ce que j'ai trouvé. Aucune exception, réflexion unique (par type).

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

La classe supplémentaire est requise car les méthodes d'extension ne sont pas autorisées dans les classes génériques. Cela permet une utilisation simple, comme illustré ci-dessous, et ne frappe la réflexion que la première fois qu'un type est utilisé.

"5643".ParseAs<int>()

3

Voici une autre option.

J'ai écrit un cours qui facilite l'enregistrement d'un nombre illimité de TryParse illimité gestionnaires. Cela me permet de faire ceci:

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

Je suis 42imprimé sur la console.

La classe est:

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

J'aime ça, mais comment feriez-vous sans génériques. Le cas d'utilisation étant la réflexion, bien sûr.
Sinaesthetic

J'ai ajouté une méthode surchargée qui fait du piratage par réflexion. S'il existe une façon plus élégante de le résoudre, je suis tous les yeux lol gist.github.com/dasjestyr/90d8ef4dea179a6e08ddd85e0dacbc94
Sinaesthetic

2

Quand j'ai voulu faire presque exactement cette chose, j'ai dû l'implémenter à la dure, après réflexion. Étant donné T, réfléchissez typeof(T)et recherchez une méthode TryParseou Parse, en l'invoquant si vous l'avez trouvée.


C'est ce que j'allais suggérer.
Steven Evers

2

Ceci est mon essai. Je l'ai fait comme un "exercice". J'ai essayé de le rendre aussi similaire à utiliser que les existants " Convert.ToX () ", etc. Mais celui-ci est une méthode d'extension:

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

Le principal inconvénient de cela par rapport à TypeConverter.ConvertFrom()est que la classe source doit fournir la conversion de type, ce qui signifie généralement que vous ne pouvez pas prendre en charge la conversion en types personnalisés.
Ian Goldby

1

Comme vous l'avez dit, TryParsene fait pas partie d'une interface. Il n'est pas non plus membre d'une classe de base donnée car il est en fait staticet les staticfonctions ne peuvent pas l'être virtual. Donc, le compilateur n'a aucun moyen de s'assurer qu'un Tmembre est réellement appelé TryParse, donc cela ne fonctionne pas.

Comme l'a dit @Mark, vous pouvez créer votre propre interface et utiliser des types personnalisés, mais vous n'avez pas de chance pour les types intégrés.


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

Il s'agit de «contraintes génériques». Parce que vous n'avez pas d'interface spécifique, vous êtes bloqué, sauf si vous suivez les suggestions de la réponse précédente.

Pour obtenir de la documentation à ce sujet, consultez le lien suivant:

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

Il vous montre comment utiliser ces contraintes et devrait vous donner quelques indices supplémentaires.


0

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

en suivant cette référence: Comment invoquer une méthode statique en C # 4.0 avec un type dynamique?

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

Et utilisez-le comme suit:

  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

J'ai réussi à obtenir quelque chose qui fonctionne comme ça

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

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

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

Le StaticMembersDynamicWrapper est adapté de l' article de David Ebbo (il lançait une AmbiguousMatchException)


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

0

Avec l' TypeDescriptorutilisation de classe de TryParsemanière connexe:

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

Bien que ce code puisse résoudre la question, y compris une explication de comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre message, et entraînerait probablement plus de votes positifs. N'oubliez pas que vous répondrez à la question des lecteurs à l'avenir, pas seulement à la personne qui pose la question maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limitations et hypothèses applicables.
double bip

0

En utilisant les informations ci-dessus, c'est ce que j'ai développé. Il convertira directement l'objet est possible, sinon il convertira l'objet en chaîne et appellera la méthode TryParse pour le type d'objet souhaité.

Je mets en cache les méthodes dans un dictionnaire à mesure que chacune est rencontrée pour réduire la charge de récupération des méthodes.

Il est possible de tester si l'objet peut être directement converti en type cible, ce qui réduirait davantage la partie de conversion de chaîne. Mais je vais laisser ça de côté pour l'instant.

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

J'ai dû ajouter une autre fonction pour prendre en charge les énumérations. Il semble que l'analyse Enum nécessite l'attribut "where T: struct", et je veux que cela fonctionne sur tout ce qui peut être converti. (devrait probablement ajouter un attribut convertible au type). Cependant, certaines des suggestions suivantes semblent plus simples (donc meilleures).
B Duffy

0

J'ai rassemblé un tas d'idées ici et je me suis retrouvé avec une solution très courte.

Il s'agit d'une méthode d'extension sur une chaîne

enter code here

Je l'ai fait avec la même empreinte que les méthodes TryParse sur les types numériques

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

Je ne vois aucun avantage à avoir une telle TryParsefonction générique . Il existe trop de stratégies différentes pour analyser et convertir des données entre différents types, avec un comportement conflictuel possible. Comment cette fonction pourrait-elle savoir quelle stratégie choisir sans contexte?

  • des classes avec des fonctions TryParse dédiées pourraient être appelées
  • les classes avec des fonctions Parse dédiées peuvent être enveloppées avec un résultat try-catch et bool
  • classes avec des surcharges d'opérateurs, comment les laisseriez-vous gérer l'analyse?
  • les descripteurs de type sont intégrés à l'aide Convert.ChangeType. Cette API est personnalisable au moment de l'exécution. Votre fonction nécessite-t-elle un comportement par défaut ou permet-elle une personnalisation?
  • devez-vous autoriser un cadre de cartographie à essayer d'analyser pour vous?
  • comment géreriez-vous les conflits ci-dessus?

-2

Une version pour obtenir des descendants de 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;
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.