Tipo anulable como parámetro genérico posible?

287

Quiero hacer algo como esto:

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

Observe el tipo anulable como parámetro genérico.

Como la GetValueOrNullfunción podría devolver nulo, mi primer intento fue este:

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 obtengo ahora es:

El tipo 'int?' debe ser un tipo de referencia para usarlo como parámetro 'T' en el tipo o método genérico

¡Correcto! Nullable<int>es un struct! Así que intenté cambiar la restricción de clase a una structrestricción (y como efecto secundario no puede volver nullmás):

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

Ahora la tarea:

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

Da el siguiente error:

El tipo 'int?' debe ser un tipo de valor no anulable para poder usarlo como parámetro 'T' en el tipo o método genérico

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

Tom Pester
fuente
3
Pls pls que su firma IDataRecordde DbDataRecord..
nawfal

Respuestas:

262

Cambie el tipo de retorno a Nullable 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;
}
Greg Dean
fuente
1
Le sugiero que use "columnValue == DBNull.Value" en lugar del operador 'is', porque es un poco más rápido =)
driAn
40
Preferencia personal, pero puede usar la forma abreviada T? en lugar de Nullable <T>
Dunc
11
Esto está bien para los tipos de valor, pero creo que no funcionará en absoluto con los tipos de referencia (por ejemplo, GetValueOrNull <string>) porque a C # no parece gustarle Nullable <(tipo de referencia)> como "cadena?". Las soluciones de Robert C Barth y James Jones, a continuación, me parecen mucho mejores si esa es su necesidad.
bacar
2
@bacar - correcto, de ahí el "where T: struct", si desea tipos de referencia puede crear un método similar con "where T: class"
Greg Dean
44
@Greg: claro, pero necesitas un segundo método y no puedes sobrecargar el nombre. Como digo, si desea manejar los tipos val y ref, creo que en esta página se presentan soluciones más limpias.
bacar
107
public static T GetValueOrDefault<T>(this IDataRecord rdr, int index)
{
    object val = rdr[index];

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

    return default(T);
}

Solo úsalo así:

decimal? Quantity = rdr.GetValueOrDefault<decimal?>(1);
string Unit = rdr.GetValueOrDefault<string>(2);
James Jones
fuente
66
Esto podría acortarse a: return rdr.IsDBNull (index)? predeterminado (T): (T) rdr [índice];
Foole el
11
Creo que esta pregunta quiere explícitamente nulo , no predeterminado (T) .
mafu
55
@mafu default (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.
James Jones
2
Creo que es más claro llamar a esto GetValueOrDefaultpara aclarar que regresa en default(T)lugar de hacerlo null. Alternativamente, podría hacer que arroje una excepción si Tno es anulable.
Sam
Este método tiene muchas ventajas y te obliga a pensar en devolver algo que no sea nulo también.
Shane
61

Simplemente haga dos cosas a su código original: elimine la whererestricción y cambie el último returnde return nulla return default(T). De esta manera, puede devolver el tipo que desee.

Por cierto, puede evitar el uso iscambiando su ifestado de cuenta a if (columnValue != DBNull.Value).

Robert C. Barth
fuente
44
Esta solución no funciona, ya que existe una diferencia lógica entre NULL y 0
Greg Dean
15
¿Funciona si el tipo que pasa es int ?. Devolverá NULL, tal como él quiere. Si pasa int como tipo, devolverá 0 ya que un int no puede ser NULL. Además del hecho de que lo probé y funciona perfectamente.
Robert C. Barth
2
Esta es la respuesta más correcta y flexible. Sin embargo, return defaultes suficiente (no necesita (T), el compilador lo inferirá del tipo de retorno de firma).
McGuireV10
5

Descargo de responsabilidad: esta respuesta funciona, pero tiene fines educativos únicamente. :) La solución de James Jones es probablemente la mejor aquí y ciertamente con la que yo iría.

La dynamicpalabra clave de C # 4.0 hace esto aún más fácil, aunque menos seguro:

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

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

Ahora no necesita la sugerencia de tipo explícito en el RHS:

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

De hecho, ¡ni siquiera lo necesitas!

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

value ahora será un int, o una cadena, o cualquier tipo que se haya devuelto desde la base de datos.

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

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

Como con todo el código que usa dynamic: codificador de advertencia.

Ian Kemp
fuente
4

Solo 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;
}
Toby
fuente
3

Creo que quieres manejar tipos de referencia y tipos de estructura. Lo uso para convertir cadenas de elementos XML a un tipo más tipeado. Puede eliminar la alternativa nula con reflexión. El proveedor de formato es manejar el dependiente de la cultura '.' o ',' separador en, por ejemplo, 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; 
            }
        }
    }

Puedes usarlo así:

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);
Roland Roos
fuente
2

Esto puede ser un hilo muerto, pero tiendo a usar lo siguiente:

public static T? GetValueOrNull<T>(this DbDataRecord reader, string columnName)
where T : struct 
{
    return reader[columnName] as T?;
}
Ryan Horch
fuente
1
"El tipo 'T' debe ser un tipo de valor no anulable para poder usarlo como parámetro 'T' en el tipo genérico o método 'Nullable <T>'"
Ian Warburton
1

Acabo de encontrar el mismo problema yo mismo.

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

Funciona con cualquier tipo sin problemas. Si el resultado es DBNull, devuelve nulo cuando falla la conversión.

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

Sé que esto es viejo, pero aquí hay 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, no le importa si Tfue 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);

nurchi
fuente
Sería bueno si trabajaras en tus convenciones de nombres. Carecen de consistencia. En un lugar hay una variable sin mayúscula, luego hay una con. Lo mismo con los parámetros de los métodos.
Marino Šimić el
1
¡Hecho y hecho! El código Hope se ve mejor ahora. Bob es tu tía :) Todo es skookum
nurchi
0

Múltiples restricciones genéricas no se pueden combinar en forma OR (menos restrictiva), solo en forma AND (más restrictiva). Lo que significa que un método no puede manejar ambos escenarios. Las restricciones genéricas tampoco se pueden usar para hacer una firma única para el método, por lo que tendría que usar 2 nombres de métodos 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 nulo, y nunca el valor predeterminado de ningún tipo de valor posible. GetValueOrDefault = bad. GetValueOrNull = bueno.

Usé las palabras "Nulo" y "Nulable" para distinguir entre tipos de referencia y tipos de valor. Y aquí hay un ejemplo de un par de métodos de extensión que escribí que complementa el método FirstOrDefault en la clase 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;
    }
Casey Plummer
fuente
0

El camino más corto:

public static T ValueOrDefault<T>(this DataRow reader, string columnName) => 
        reader.IsNull(columnName) ? default : (T) reader[columnName];

volver 0para inty nullparaint?

Amirhossein Yari
fuente