web-dev-qa-db-fra.com

Comment puis-je résoudre ce problème pour effectuer une conversion générique en Nullable <T>?

J'utilise actuellement cette méthode pratique d'extension de conversion pour effectuer des conversions entre types:

    public static T To<T>(this IConvertible obj)
    {
        return (T)Convert.ChangeType(obj, typeof(T));
    }

Cependant, cela n'aime pas la conversion de valeurs valides en Nullable, par exemple, cela échoue:

    "1".To<int?>();

Évidemment, 1 est facilement converti en un (int?), Mais cela donne l'erreur:

    Invalid cast from 'System.String' to 'System.Nullable`1[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'.

Ceci est évidemment un exemple simplifié. En réalité, je l’utilise pour convertir des types de chaînes comme suit:

packageDb.Quantity = package.package.ElementDeep(Namespace + "PackageQuantity", Namespace + "ActualQuantity", Namespace + "Quantity").ValueOrNull().To<int?>();

Si Convert.ChangeType n'aime pas Nullable, est-ce que quelqu'un a de bonnes idées?

45
TheSoftwareJedi
public static T To<T>(this IConvertible obj)
{
    Type t = typeof(T);
    Type u = Nullable.GetUnderlyingType(t);

    if (u != null)
    {
        return (obj == null) ? default(T) : (T)Convert.ChangeType(obj, u);
    }
    else
    {
        return (T)Convert.ChangeType(obj, t);
    }
}
95
LukeH
public static T To<T>(this IConvertible obj) 
{
    Type t = typeof(T);
    if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
        t = t.GetGenericArguments()[0];

    return (T)Convert.ChangeType(obj, t); 
}

Mais si la conversion échoue, il lève une exception, ne renvoyant pas une valeur null comme prévu.

4
Henrique

J'ai fini avec ça

private static T To<T>(this Object @object, Boolean returnDefaultOnException)
{
    Type type = typeof(T);
    Type underlyingTypeOfNullable = Nullable.GetUnderlyingType(type);
    try
    {
        return (T) Convert.ChangeType(@object, underlyingTypeOfNullable ?? type);
    }
    catch (Exception exception)
    {
        if (returnDefaultOnException)
            return default(T);
        String typeName = type.Name;
        if (underlyingTypeOfNullable != null)
            typeName += " of " + underlyingTypeOfNullable.Name;
        throw new InvalidCastException("Object can't be cast to " + typeName, exception);

    }
}
public static T To<T>(this Object @object) { return @object.To<T>(returnDefaultOnException: false); }
public static T ToOrDefault<T>(this Object @object) { return @object.To<T>(returnDefaultOnException: true); }

Il se comporte comme les méthodes d'extension LINQ Single et SingleOrDefault et First et FirstOrDefault.

En bref, To<T>() tente de convertir et lance en cas d'échec, tandis que ToOrDefault<T>() tente de convertir et renvoie default(T) en cas d'échec.

3
Nick Strupat

Peut-être que je manque le point, mais dans le cas de Nullable, comment votre méthode offre-t-elle un avantage en termes de lisibilité, de performances ou de maintenance par rapport à une distribution simple, telle que (int?)1?

En dehors de cela, peut-être une autre méthode d'extension? 

public static T? ToNullable<T>(this T obj) where T:struct
{
    return (T?)obj;
}

Modifier  

Après avoir examiné votre modification, pourquoi la fonction générique que j’ai fournie ne fonctionne-t-elle pas comme substitut à votre fonction To<T> dans cette ligne de code? Vous ne pouvez pas autoriser une conversion en Nullable pour quelque type que ce soit (c'est pourquoi ChangeType ne fonctionne pas) car ce type générique n'accepte que les types de valeur. Vous devrez soit utiliser une fonction comme celle que j'ai fournie ou modifier votre signature de To<T> pour n'accepter que les types de valeur et ajouter un cas spécial pour Nullable<T>.

2
Adam Robinson

La solution de Luke était bonne pour moi (et a évidemment obtenu son vote positif) mais je l'ai simplifiée pour moi de cette façon

    private static Type ResolveType(String typeName)
    {
        Type t = Type.GetType(typeName);
        if (t == null)
            return null;

        Type u = Nullable.GetUnderlyingType(t);

        if (u != null) {
            t = u;
        }
        return t;
    }

parce que je suis parti d'une chaîne pas d'un type ... pensées?

2
Eugenio Miró

C'est la méthode que j'utilise actuellement (j'ai eu ma réponse sur SO ), elle convertit de chaîne en type nullable:

    public static Nullable<T> ConvertToNullable<T>(this string s) where T : struct
    {
        if (!string.IsNullOrEmpty(s.Trim()))
        {
            TypeConverter conv = TypeDescriptor.GetConverter(typeof(Nullable<>).MakeGenericType(typeof(T)));
            return (Nullable<T>)conv.ConvertFrom(s);
        }
        return null;
    }
1
Nathan Koop

Cette méthode fait ce dont vous avez besoin, et elle a l'air bien en la faisant.

    /// <summary>
    /// <para>More convenient than using T.TryParse(string, out T). 
    /// Works with primitive types, structs, and enums.
    /// Tries to parse the string to an instance of the type specified.
    /// If the input cannot be parsed, null will be returned.
    /// </para>
    /// <para>
    /// If the value of the caller is null, null will be returned.
    /// So if you have "string s = null;" and then you try "s.ToNullable...",
    /// null will be returned. No null exception will be thrown. 
    /// </para>
    /// <author>Contributed by Taylor Love (Pangamma)</author>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="p_self"></param>
    /// <returns></returns>
    public static T? ToNullable<T>(this string p_self) where T : struct
    {
        if (!string.IsNullOrEmpty(p_self))
        {
            var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(T));
            if (converter.IsValid(p_self)) return (T)converter.ConvertFromString(p_self);
            if (typeof(T).IsEnum) { T t; if (Enum.TryParse<T>(p_self, out t)) return t;}
        }

        return null;
    }

https://github.com/Pangamma/PangammaUtilities-CSharp/tree/master/src/StringExtensions

0
Pangamma

étendre le code @LukeH:

public static T GetValue<T>(string Literal, T DefaultValue)
    {
        if (Literal == null || Literal == "" || Literal == string.Empty) return DefaultValue;
        IConvertible obj = Literal;
        Type t = typeof(T);
        Type u = Nullable.GetUnderlyingType(t);

        if (u != null)
        {
            return (obj == null) ? DefaultValue : (T)Convert.ChangeType(obj, u);
        }
        else
        {
            return (T)Convert.ChangeType(obj, t);
        }
    }
0
Hossein Shahabi