web-dev-qa-db-fra.com

Type Nullable en tant que paramètre générique possible?

Je veux faire quelque chose comme ça:

myYear = record.GetValueOrNull<int?>("myYear"),

Notez le type nullable en tant que paramètre générique.

Puisque la fonction GetValueOrNull pouvait renvoyer null, ma première tentative était la suivante:

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : class
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
    {
        return (T)columnValue;
    }
    return null;
}

Mais l'erreur que je reçois maintenant est la suivante:

Le type 'int?' doit être un type de référence pour pouvoir être utilisé comme paramètre 'T' dans le type ou la méthode générique

Droite! Nullable<int> est un struct! J'ai donc essayé de changer la contrainte de classe en une contrainte struct (et comme effet secondaire, je ne peux plus retourner null):

public static T GetValueOrNull<T>(this DbDataRecord reader, string columnName)
  where T : struct

Maintenant la mission:

myYear = record.GetValueOrNull<int?>("myYear");

Donne l'erreur suivante:

Le type 'int?' doit être un type de valeur non nullable pour pouvoir être utilisé comme paramètre 'T' dans le type ou la méthode générique

La spécification d'un type nullable en tant que paramètre générique est-elle possible?

256
Tom Pester

Définissez le type de retour sur Nullable et appelez la méthode avec le paramètre non nullable.

static void Main(string[] args)
{
    int? i = GetValueOrNull<int>(null, string.Empty);
}


public static Nullable<T> GetValueOrNull<T>(DbDataRecord reader, string columnName) where T : struct
{
    object columnValue = reader[columnName];

    if (!(columnValue is DBNull))
        return (T)columnValue;

    return null;
}
239
Greg Dean
public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

    if (!(val is DBNull))
        return (T)val;

    return default(T);
}

Il suffit de l'utiliser comme ceci:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);
102
James Jones

Faites juste deux choses avec votre code original - supprimez la contrainte where, et remplacez le dernier return de return null par return default(T). De cette façon, vous pouvez retourner le type que vous voulez.

En passant, vous pouvez éviter l'utilisation de is en modifiant votre instruction if en if (columnValue != DBNull.Value).

57
Robert C. Barth

Disclaimer: Cette réponse fonctionne, mais est uniquement destinée à des fins éducatives. :) La solution de James Jones est probablement la meilleure ici et certainement celle avec laquelle j'irais.

Le mot clé dynamic de C # 4.0 facilite encore cette opération, si celle-ci est moins sûre:

public static dynamic GetNullableValue(this IDataRecord record, string columnName)
{
  var val = reader[columnName];

  return (val == DBNull.Value ? null : val);
}

Maintenant, vous n'avez plus besoin d'indications de type explicites sur le RHS:

int? value = myDataReader.GetNullableValue("MyColumnName");

En fait, vous n'en avez même pas besoin du tout!

var value = myDataReader.GetNullableValue("MyColumnName");

value sera désormais un entier, une chaîne ou le type retourné par la base de données.

Le seul problème est que cela ne vous empêche pas d'utiliser des types non nullables sur le LHS, auquel cas vous obtiendrez une exception d'exécution plutôt désagréable comme:

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: Cannot convert null to 'int' because it is a non-nullable value type

Comme avec tout code utilisant dynamic: avertisseur.

5
Ian Kemp

Juste dû faire quelque chose d'incroyable semblable à ceci. Mon code:

public T IsNull<T>(this object value, T nullAlterative)
{
    if(value != DBNull.Value)
    {
        Type type = typeof(T);
        if (type.IsGenericType && 
            type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
        {
            type = Nullable.GetUnderlyingType(type);
        }

        return (T)(type.IsEnum ? Enum.ToObject(type, Convert.ToInt32(value)) :
            Convert.ChangeType(value, type));
    }
    else 
        return nullAlternative;
}
4
Toby

Je pense que vous voulez gérer les types de référence et les types de structure. Je l'utilise pour convertir des chaînes d'élément XML en un type plus typé. Vous pouvez supprimer la nullAlternative avec réflexion. Le fournisseur de format est de gérer la culture dépendante '.' ou ',' séparateur dans par ex. décimales ou ints et doubles. Cela peut fonctionner:

public T GetValueOrNull<T>(string strElementNameToSearchFor, IFormatProvider provider = null ) 
    {
        IFormatProvider theProvider = provider == null ? Provider : provider;
        XElement Elm = GetUniqueXElement(strElementNameToSearchFor);

        if (Elm == null)
        {
            object o =  Activator.CreateInstance(typeof(T));
            return (T)o; 
        }
        else
        {
            try
            {
                Type type = typeof(T);
                if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(Nullable<>).GetGenericTypeDefinition())
                {
                    type = Nullable.GetUnderlyingType(type);
                }
                return (T)Convert.ChangeType(Elm.Value, type, theProvider); 
            }
            catch (Exception)
            {
                object o = Activator.CreateInstance(typeof(T));
                return (T)o; 
            }
        }
    }

Vous pouvez l'utiliser comme ceci:

iRes = helper.GetValueOrNull<int?>("top_overrun_length");
Assert.AreEqual(100, iRes);



decimal? dRes = helper.GetValueOrNull<decimal?>("top_overrun_bend_degrees");
Assert.AreEqual(new Decimal(10.1), dRes);

String strRes = helper.GetValueOrNull<String>("top_overrun_bend_degrees");
Assert.AreEqual("10.1", strRes);
3
Roland Roos

Cela peut être un fil mort, mais j'ai tendance à utiliser ce qui suit:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}
2
Ryan Horch

Je sais que c'est vieux, mais voici une autre solution:

public static bool GetValueOrDefault<T>(this SqlDataReader Reader, string ColumnName, out T Result)
{
    try
    {
        object ColumnValue = Reader[ColumnName];

        Result = (ColumnValue!=null && ColumnValue != DBNull.Value) ? (T)ColumnValue : default(T);

        return ColumnValue!=null && ColumnValue != DBNull.Value;
    }
    catch
    {
        // Possibly an invalid cast?
        return false;
    }
}

Maintenant, vous ne vous souciez pas de savoir si T était un type valeur ou référence. Si la fonction renvoie true, la base de données vous donne une valeur raisonnable. Usage:

...
decimal Quantity;
if (rdr.GetValueOrDefault<decimal>("YourColumnName", out Quantity))
{
    // Do something with Quantity
}

Cette approche est très similaire à int.TryParse("123", out MyInt);

1
nurchi

Je viens de rencontrer le même problème moi-même.

... = reader["myYear"] as int?; fonctionne et est propre.

Cela fonctionne avec n'importe quel type sans problème. Si le résultat est DBNull, il renvoie la valeur null lorsque la conversion échoue.

1
Hele

Plusieurs contraintes génériques ne peuvent pas être combinées de manière OR (moins restrictive), uniquement de manière AND (plus restrictive). Ce qui signifie qu'une méthode ne peut pas gérer les deux scénarios. Les contraintes génériques ne peuvent pas non plus être utilisées pour créer une signature unique pour la méthode. Vous devez donc utiliser deux noms de méthode distincts.

Toutefois, vous pouvez utiliser les contraintes génériques pour vous assurer que les méthodes sont utilisées correctement.

Dans mon cas, je voulais spécifiquement que null soit renvoyé et jamais la valeur par défaut des types de valeur possibles. GetValueOrDefault = bad. GetValueOrNull = bon.

J'ai utilisé les mots "Null" et "Nullable" pour distinguer les types de référence des types de valeur. Et voici un exemple de quelques méthodes d'extension que j'ai écrites et qui complimentent la méthode FirstOrDefault de la classe System.Linq.Enumerable.

    public static TSource FirstOrNull<TSource>(this IEnumerable<TSource> source)
        where TSource: class
    {
        if (source == null) return null;
        var result = source.FirstOrDefault();   // Default for a class is null
        return result;
    }

    public static TSource? FirstOrNullable<TSource>(this IEnumerable<TSource?> source)
        where TSource : struct
    {
        if (source == null) return null;
        var result = source.FirstOrDefault();   // Default for a nullable is null
        return result;
    }
0
Casey