2008-10-16 15 views
220

quiero hacer algo como esto:Tipo anulable como un parámetro genérico posible?

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

Aviso del tipo anulable como el parámetro genérico.

Dado que la función GetValueOrNull podría devolver null mi primer intento fue la siguiente:

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

Pero el error que estoy recibiendo ahora es:

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

derecho! Nullable<int> es un struct! Así que he intentado cambiar la restricción de clase a una restricción struct (y como un efecto secundario no puede volver null más):

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

Ahora la misión:

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

da el siguiente error:

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

¿Es posible especificar un tipo anulable como parámetro genérico?

+1

Pls pls hacen su firma '' IDataRecord' de DbDataRecord' .. – nawfal

Respuesta

205

Cambiar el tipo de retorno a anulable, y llame al método con el parámetro no anulable

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

le sugiero que utilice "columnValue == DBNull.Valor "en lugar del operador 'es', porque es ligeramente más rápido =) – driAn

+29

Preferencia personal, pero puede usar la forma abreviada T? En lugar de anulable Dunc

+8

Esto está bien para los tipos de valor, pero entonces creo que no funcionará en absoluto con tipos de referencia (por ejemplo, GetValueOrNull ) porque C# no parece gustar Nullable <(ref type)> como "cadena?" Las soluciones de Robert C Barth & James Jones, a continuación, me parecen mucho mejor si esa es su necesidad. – bacar

39

Sólo hacer dos cosas a su código original - eliminar la restricción where, y cambiar el último returnreturn null-return default(T) . De esta forma puede devolver el tipo que desee.

Por cierto, puede evitar el uso de is cambiando su declaración if al if (columnValue != DBNull.Value).

+3

Esta solución no funciona, ya que existe una diferencia lógica entre NULL y 0 –

+11

Funciona si el tipo que pasa es int ?. Devolverá NULL, como él quiere. Si pasa int como el tipo, devolverá 0 ya que un int no puede ser NULL. Además del hecho de que lo probé y funciona perfectamente. –

+0

Esta es la respuesta más correcta y flexible. Sin embargo, 'return default' es suficiente (no necesita' (T) ', el compilador lo deducirá del tipo de retorno de firma). – McGuireV10

4

Simplemente tenía que hacer algo increíble similar a esto. Mi código:

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

sólo lo utilizan como esto:

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

Esto se puede acortar a: return rdr.IsDBNull (index)? predeterminado (T): (T) rdr [índice]; – Foole

+6

Creo que esta pregunta explícitamente quiere _null_, not _default (T) _. – mafu

+1

@mafu predeterminado (T) devolverá nulo para los tipos de referencia, y 0 para los tipos numéricos, lo que hace que la solución sea más flexible. –

3

creo que quiere manejar los tipos de referencia y los tipos struct. Lo uso para convertir cadenas de elementos XML a un tipo más tipeado. Puede eliminar nullAlternative con reflejo. El proveedor de formato es para manejar el dependiente de cultura '.' o ',' separador en, p. decimales o ints y dobles. Esto puede funcionar:

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

Usted puede utilizar de esta manera:

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

responsabilidad: trabaja Esta respuesta, pero está destinado sólo con fines educativos. :) La solución de James Jones es probablemente la mejor aquí y ciertamente la que yo elegiría.

de C# 4.0 dynamic palabra clave que hace esto aún más fácil, aunque menos segura:

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

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

Ahora usted no necesita la insinuación de tipo explícita en el lado derecho:

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

De hecho, ¡Ni siquiera lo necesitas para nada!

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

value ahora habrá un int, o una cadena, o cualquier tipo fue devuelto de la BD.

El único problema es que esto no le impide el uso de tipos no anulable en la LHS, en cuyo caso se obtendrá una excepción de ejecución bastante desagradable como:

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

Al igual que con todo el código que se utiliza dynamic: código de advertencia.

2

Esto puede ser un hilo muerto, pero tienden a utilizar los siguientes:

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

"El tipo 'T' debe ser un tipo de valor que no admite nulos para usarlo como parámetro 'T' en el tipo genérico o método 'Anulable '" –

1

Sé que esto es viejo, pero aquí es otra solución:

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

Ahora, usted don' No importa si T era un valor o tipo de referencia. Solo si la función devuelve verdadero, tiene un valor razonable de la base de datos. Uso:

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

Este enfoque es muy similar a int.TryParse("123", out MyInt);

+0

Sería bueno si trabajó en sus convenciones de nomenclatura. Ellos carecen de consistencia. En un lugar hay una variable sin capital, entonces hay una con. Lo mismo con los parámetros de los métodos. –

+0

¡Hecho y hecho! El código de esperanza se ve mejor ahora. Bob es tu tía :) Todo es skookum – nurchi

1

simplemente me encontré con el mismo problema a mí mismo.

... = reader["myYear"] as int?; funciona y está limpio.

Funciona con cualquier tipo sin problemas. Si el resultado es DBNull, devuelve null ya que la conversión falla.

+0

De hecho, probablemente puedas hacer 'int v = reader [" myYear "] ?? - 1;' o algún otro valor predeterminado en lugar de '-1'. Sin embargo, esto podría provocar problemas si el valor es 'DBNull' ... – nurchi

0

Las restricciones genéricas múltiples no se pueden combinar de forma OR (menos restrictivas), solo de forma Y (más restrictivas). Lo que significa que un método no puede manejar ambos escenarios. Las restricciones genéricas tampoco se pueden usar para crear una firma única para el método, por lo que tendría que usar 2 nombres de método separados.

Sin embargo, puede usar las restricciones genéricas para asegurarse de que los métodos se usan correctamente.

En mi caso, específicamente quería que se devolviera el valor nulo, y nunca el valor predeterminado de ningún tipo de valor posible. GetValueOrDefault = malo. GetValueOrNull = bueno.

Utilicé las palabras "Nulo" y "Anulable" para distinguir entre tipos de referencia y tipos de valores. Y aquí hay un ejemplo de un par de métodos de extensión que escribí que complementa el método FirstOrDefault en System.Linq.Enumerable class.

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