2008-11-19 24 views
11

que tienen una serie de métodos de extensión para ayudar con nula comprobación en objetos IDataRecord, que estoy actualmente de aplicación como esta:¿Puede un método genérico manejar los tipos de valores de referencia y valores anulables?

public static int? GetNullableInt32(this IDataRecord dr, int ordinal) 
{ 
    int? nullInt = null; 
    return dr.IsDBNull(ordinal) ? nullInt : dr.GetInt32(ordinal); 
} 

public static int? GetNullableInt32(this IDataRecord dr, string fieldname) 
{ 
    int ordinal = dr.GetOrdinal(fieldname); 
    return dr.GetNullableInt32(ordinal); 
} 

y así sucesivamente, para cada tipo tengo que tratar.

Me gustaría volver a implementar esto como un método genérico, en parte para reducir la redundancia y en parte para aprender a escribir métodos genéricos en general.

He escrito esto:

public static Nullable<T> GetNullable<T>(this IDataRecord dr, int ordinal) 
{ 
    Nullable<T> nullValue = null; 
    return dr.IsDBNull(ordinal) ? nullValue : (Nullable<T>) dr.GetValue(ordinal); 
} 

que funciona siempre y cuando T es un tipo de valor, pero si T es un tipo de referencia no lo hará.

Este método debería devolver un tipo anulable si T es un tipo de valor y, por defecto, (T). ¿Cómo implementaría este comportamiento?

Respuesta

10

puede declarar su método como este:

public static T GetNullable<T>(this IDataRecord dr, int ordinal) 
{ 
    return dr.IsDBNull(ordinal) ? default(T) : (T) dr.GetValue(ordinal); 
} 

De esta manera, si T es un int anulable o cualquier otro tipo de valor que admite nulos, de hecho devolverá nulo. Si se trata de un tipo de datos normal, simplemente devolverá el valor predeterminado para ese tipo.

+2

Es una muy mala solución. Si está en el valor NULL del registro de datos, no desea un valor predeterminado de int. NULL y ZERO son valores diferentes. – TcKs

+5

SI el valor en la base de datos es nulo, entonces el T sería un Nullable en cuyo caso NULL es el valor predeterminado que se devolvería. – BFree

+2

Si es así, ¿por qué usar este método de extensión, si puede usar simplemente "dr [ordinal] como int?" ? – TcKs

-2

lo hago de esta manera:

DataRow record = GetSomeRecord(); 
int? someNumber = record[15] as int? 
Guid? someUID = record["MyPrimaryKey"] as Guid?; 
string someText = GetSomeText(); 
record["Description"] = someText.ToDbString(); 

// ........ 

public static class StringExtensionHelper { 
    public static object ToDbString(this string text) { 
     object ret = null != text ? text : DBNull.Value 
     return ret; 
    } 
} 

EDIT: Usted puede (o debe) tener un "ToDbInt32, ToDbBool, etc ..." métodos de extensión para otros tipos primitivos por supuesto.

EDIT 2: También puede ampliar la clase base "objeto" con "ToDbValue".

public static class StringExtensionHelper { 
    public static object ToDbValue(this object value) { 
     object ret = object.ReferenceEquals(value, null) ? (object)DBNull.Value : value; 
     return ret; 
    } 
} 
+0

Pude, pero el objetivo de este ejercicio de genéricos es evitar escribir un método diferente para cada tipo de datos. Y no veo cómo su ejemplo es pertinente ... Extiendo IDataRecord para null verifique los datos tomados _desde_ el almacén de datos. –

+0

Si desea verificar los valores del registro, puede usar la palabra clave "como", que hace lo que desea. Si está en el registro DbNull, se devolverá el valor nulo. De lo contrario, se devolverá "int" como "Nullable ". No se requiere un método especial. – TcKs

+0

Debería ** no ** extender el objeto con un método de extensión ya que no estará disponible en todos los idiomas, como VB.NET –

-1

La estructura anulable es sólo para los tipos de valor, porque los tipos de referencia son anulables todos modos ...

+2

Por eso no puedo hacer que este devuelva Nullable , por lo que hice la pregunta. –

1

No creo que pueda implementar esto con una sola función. Si C# admite la sobrecarga en función del tipo de devolución, es posible que pueda hacerlo, pero incluso así recomendaría que no lo haga.

Debería poder lograr lo mismo al no usar tipos de datos que aceptan nulos y devolver un valor real o nulo, como suggested por BFree.

+0

En realidad, puede tener dos métodos sobrecargados que solo difieren en los tipos de devolución. Esta es la sintaxis legítima: cadena pública Método() { return ""; } public int Método() { return 0; } – BFree

+0

@BFree: Escriba 'xyz' ya define un miembro llamado 'Método' con los mismos tipos de parámetros. –

+0

@BFree: Acabo de probar el código exacto y obtuve el siguiente error: Escriba 'Programa1' ya define un miembro llamado 'Método' con los mismos tipos de parámetros. –

2

Esto funciona:

public static T Get<T>(this IDataRecord dr, int ordinal) 
{ 
    T nullValue = default(T); 
    return dr.IsDBNull(ordinal) ? nullValue : (T) dr.GetValue(ordinal); 
} 


public void Code(params string[] args) 
{ 
    IDataRecord dr= null; 
    int? a = Get<int?>(dr, 1); 
    string b = Get<string>(dr, 2); 
} 
+1

Esto es más o menos lo mismo que la respuesta de BFree. El uso de la muestra es útil. –

0

No se puede hacer con un método, pero lo hace con tres:

public static T GetData<T>(this IDataReader reader, Func<int, T> getFunc, int index) 
{ 
    if (!reader.IsClosed) 
    { 
     return getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

public static T GetDataNullableRef<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : class 
{ 
    if (!reader.IsClosed) 
    { 
     return reader.IsDBNull(index) ? null : getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

public static T? GetDataNullableValue<T>(this IDataReader reader, Func<int, T> getFunc, int index) where T : struct 
{ 
    if (!reader.IsClosed) 
    { 
     return reader.IsDBNull(index) ? (T?)null : getFunc(index); 
    } 
    throw new ArgumentException("Reader is closed.", "reader"); 
} 

Luego de usar que puede hacer:

private static Whatever CreateObject(IDataReader reader) 
{ 
    Int32? id = reader.GetDataNullableValue<Int32>(reader.GetInt32, 0); 
    string name = reader.GetDataNullableRef<string>(reader.GetString, 1); 
    Int32 x = reader.GetData<Int32>(reader.GetInt32, 2); 
} 
0
public static T Get<T>(this IDataRecord rec, Func<int, T> GetValue, int ordinal) 
{ 
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(ordinal); 
} 

o más performant

public static T Get<T>(this IDataRecord rec, Func<IDataRecord, int, T> GetValue, int ordinal) 
{ 
    return rec.IsDBNull(ordinal) ? default(T) : GetValue(rec, ordinal); 
} 

public static Func<IDataRecord, int, int> GetInt32 = (rec, i) => rec.GetInt32(i); 
public static Func<IDataRecord, int, bool> GetBool = (rec, i) => rec.GetBoolean(i); 
public static Func<IDataRecord, int, string> GetString = (rec, i) => rec.GetString(i); 

y utilizar de esta manera

rec.Get(GetString, index); 
rec.Get(GetInt32, index); 
+0

Ha logrado implementar una función genérica sin obtener el más mínimo beneficio de ella. No tener que escribir una función separada para cada tipo es el _nombre_ de los genéricos, y la razón por la cual se publica esta pregunta. –

+0

Tal vez, pero la diferencia con su solución es que solo tiene un método en el que verifica la nulabilidad y evita el lanzamiento, que puede ser costoso si tiene conjuntos de datos realmente grandes. – SeeR

+0

Comparando también con la solución BFree evitas el boxeo – SeeR

1

No puedo entender por qué la necesidad de complicar todo este proceso. ¿Por qué no mantenerlo simple y agradable y utilizar las siguientes líneas de código:

Para los tipos de valor donde null es válido use int? iNullable = dr[ordinal] as int?;.

Para los tipos de valor donde null no es válido, use int iNonNullable = dr[ordinal] as int? ?? default(int);.

Para tipos de referencia use string sValue = dr[ordinal] as string;.

Para cualquiera que piense que el código no funcionará y que dr[ordinal] arrojará una excepción para DBNull, aquí hay un método de ejemplo que una vez que se le da una cadena de conexión válida demostrará el concepto.

private void Test() 
{ 
    int? iTestA; 
    int? iTestB; 
    int iTestC; 
    string sTestA; 
    string sTestB; 

    //Create connection 
    using (SqlConnection oConnection = new SqlConnection(@"")) 
    { 
    //Open connection 
    oConnection.Open(); 

    //Create command 
    using (SqlCommand oCommand = oConnection.CreateCommand()) 
    { 
     //Set command text 
     oCommand.CommandText = "SELECT null, 1, null, null, '1'"; 

     //Create reader 
     using (SqlDataReader oReader = oCommand.ExecuteReader()) 
     { 
     //Read the data 
     oReader.Read(); 

     //Set the values 
     iTestA = oReader[0] as int?; 
     iTestB = oReader[1] as int?; 
     iTestC = oReader[2] as int? ?? -1; 
     sTestA = oReader[3] as string; 
     sTestB = oReader[4] as string; 
     } 
    } 
    } 
} 
+0

como ya expliqué a BFree en los comentarios, dr [ordinal] arroja una excepción si es DBNull. Tu ejemplo no funcionará –

+0

@ Adam Lassek - No sé por qué crees que mi código no funcionará, ¿lo has intentado? Lo tengo funcionando en sistemas de producción, por favor revise el código antes de votar. Creo que la mayoría de los noobs podrían decir que 'dr [ordinal]' no lanzará una excepción si el valor es DBNull. – stevehipwell

+0

¿qué versión de marco? En el momento en que escribí esta pregunta, estaba usando 3.5 y definitivamente lanzó una excepción si el campo era DBNull. –

Cuestiones relacionadas