2008-10-16 6 views
220

Je veux faire quelque chose comme ceci:Type nullable en tant que paramètre générique possible?

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

Notez le type Nullable comme paramètre générique.

Puisque la fonction GetValueOrNull pourrait revenir nulle 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 est maintenant:

The type 'int?' must be a reference type in order to use it as parameter 'T' in the generic type or method

droit! Nullable<int> est un struct! J'ai donc essayé de changer la contrainte de classe à une contrainte struct (et comme un effet secondaire ne peut pas revenir null plus):

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:

The type 'int?' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method

Spécifie-t-on un type nullable comme un paramètre générique du tout possible?

+1

Pls pls faire votre signature '' IDataRecord' de DbDataRecord' .. – nawfal

Répondre

205

Modifier le type de retour à Nullable et appeler la méthode avec le non paramètre annulable

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

Je vous suggère d'utiliser « ColumnValue == DBNull.Valeur "à la place de l'opérateur" est ", car il est légèrement plus rapide =) – driAn

+29

Préférence personnelle, mais vous pouvez utiliser le formulaire court T? Au lieu de Nullable Dunc

+8

Ceci est bien pour les types de valeur, mais alors je pense que cela ne fonctionnera pas du tout avec des types de référence (par exemple GetValueOrNull ) parce que C# ne semble pas aimer Nullable <(ref type)> comme "chaîne?" Les solutions de Robert C Barth & James Jones, ci-dessous, me paraissent bien meilleures si c'est votre besoin – bacar

39

Il suffit de faire deux choses à votre code d'origine - supprimer la contrainte where et changer la dernière returnreturn null-return default(T) . De cette façon, vous pouvez retourner le type que vous voulez. Par ailleurs, vous pouvez éviter l'utilisation de is en remplaçant votre instruction if par if (columnValue != DBNull.Value).

+3

Cette solution ne fonctionne pas, car il existe une différence logique entre NULL et 0 –

+11

Cela fonctionne si le type qu'il passe est int ?. Il renverra NULL, juste comme il veut. S'il passe int comme type, il retournera 0 car un int ne peut pas être NULL. Outre le fait que je l'ai essayé et ça fonctionne parfaitement. –

+0

C'est la réponse la plus correcte et la plus flexible. Cependant, 'return default' est suffisant (vous n'avez pas besoin du' (T) ', le compilateur le déduira du type de retour de la signature). – McGuireV10

4

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; 
} 
91
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 d'utiliser comme ceci:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1); 
string Unit = rdr.GetValueOrDefault<string>(2); 
+4

Cela pourrait être raccourci à: return rdr.IsDBNull (index)? par défaut (T): (T) rdr [index]; – Foole

+6

Je pense que cette question veut explicitement _null_, pas _default (T) _. – mafu

+1

@mafu default (T) renvoie null pour les types de référence, et 0 pour les types numériques, ce qui rend la solution plus flexible. –

3

Je pense que vous voulez gérer les types de référence et types struct. Je l'utilise pour convertir des chaînes d'éléments XML en un type plus typé. Vous pouvez supprimer le nullAlternative avec la réflexion. Le formatprovider doit gérer la culture dépendant '.' ou ',' séparateur dans e.g. 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); 
4

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

C# 4.0 de dynamic mot-clé rend encore plus facile, si moins sûr:

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

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

Maintenant, vous n'avez pas besoin du type hinting explicite sur l'ERS:

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

En fait, tu n'en as même pas besoin du tout!

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

value va maintenant être un entier ou une chaîne, ou quel que soit le type a été renvoyé de la DB.

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

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

Comme tout code utilise dynamic: codeur caveat.

2

Cela peut être un fil mort, mais je tendance à utiliser les éléments suivants:

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

"Le type 'T' doit être un type de valeur non nullable afin de l'utiliser comme paramètre 'T' dans le type générique ou la méthode 'Nullable '" –

1

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 n » t attention si T était valeur ou type de référence. Seulement si la fonction renvoie true, vous avez une valeur raisonnable dans la base de données. Utilisation:

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

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

+0

Ce serait bien si vous travailliez sur vos conventions de nommage. Ils manquent de cohérence. Dans un endroit il y a une variable sans capital alors il y en a une avec. La même chose avec des paramètres aux méthodes. –

+0

Fait et fait! Le code d'espoir semble mieux maintenant. Bob est votre tante :) Tout est skookum – nurchi

1

Je viens moi-même rencontré le même problème.

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

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

+0

En fait, vous pourriez probablement faire 'int v = lecteur [" myYear "] ?? - 1;' ou un autre par défaut au lieu de '-1'. Cependant, cela pourrait soulever des problèmes si la valeur est 'DBNull' ... – nurchi

0

Les contraintes génériques multiples ne peuvent pas être combinées de manière OR (moins restrictive), seulement de façon ET (plus restrictive). Cela 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, donc vous devrez utiliser deux noms de méthodes distincts. Cependant, vous pouvez utiliser les contraintes génériques pour vous assurer que les méthodes sont correctement utilisées.

Dans mon cas, je voulais spécifiquement que null soit retourné, et jamais la valeur par défaut de tous les types de valeurs possibles. GetValueOrDefault = incorrect. GetValueOrNull = bien.

J'ai utilisé les mots «Null» et «Nullable» pour faire la distinction entre les types de référence et les types de valeur. Et voici un exemple de méthodes d'extension de couple que j'ai écrites qui complimente la méthode FirstOrDefault dans 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; 
    } 
Questions connexes