Tipo nullable come parametro generico possibile?


287

Voglio fare qualcosa del genere:

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

Notare il tipo nullable come parametro generico.

Poiché la GetValueOrNullfunzione potrebbe restituire null il mio primo tentativo è stato questo:

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

Ma l'errore che sto ricevendo ora è:

Il tipo "int?" deve essere un tipo di riferimento per usarlo come parametro 'T' nel tipo o metodo generico

Destra! Nullable<int>è un struct! Quindi ho provato a cambiare il vincolo di classe in un structvincolo (e come effetto collaterale non può più tornare null):

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

Ora il compito:

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

Fornisce il seguente errore:

Il tipo "int?" deve essere un tipo di valore non annullabile per usarlo come parametro 'T' nel tipo o metodo generico

È possibile specificare un tipo nullable come parametro generico?


3
Per favore, fai la tua firma IDataRecordda DbDataRecord...
nawfal

Risposte:


262

Modificare il tipo restituito su Nullable e chiamare il metodo con il parametro 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;
}

1
Ti suggerisco di usare "columnValue == DBNull.Value" invece dell'operatore 'is', perché è leggermente più veloce =)
driAn

40
Preferenza personale, ma è possibile utilizzare la forma abbreviata T? invece di Nullable <T>
Dunc

11
Questo va bene per i tipi di valore, ma poi penso che non funzionerà affatto con i tipi di riferimento (ad esempio GetValueOrNull <string>) perché a C # non sembra piacere Nullable <(tipo di riferimento)> come "stringa?". Le soluzioni di Robert C Barth e James Jones, di seguito, mi sembrano molto meglio se questo è il tuo bisogno.
bacar,

2
@bacar - giusto, da qui "dove T: struct", se vuoi tipi di riferimento puoi creare un metodo simile con "dove T: classe"
Greg Dean,

4
@Greg - certo, ma poi hai bisogno di un secondo metodo e non puoi sovraccaricare il nome. Come ho detto, se si desidera gestire entrambi i tipi val e ref, penso che le soluzioni più pulite siano presentate in questa pagina.
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);
}

Usalo in questo modo:

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

6
Questo potrebbe essere abbreviato in: return rdr.IsDBNull (indice)? default (T): (T) rdr [indice];
Foole,

11
Penso che questa domanda voglia esplicitamente null , non default (T) .
mafu,

5
@mafu default (T) restituirà null per i tipi di riferimento e 0 per i tipi numerici, rendendo la soluzione più flessibile.
James Jones,

2
Penso che sia più chiaro chiamare questo GetValueOrDefaultper chiarire che ritorna default(T)piuttosto che null. In alternativa, potresti fargli generare un'eccezione se Tnon è nullable.
Sam,

Questo metodo ha molti vantaggi e ti costringe a pensare di restituire qualcosa di diverso da null.
Shane,

61

Basta fare due cose al codice originale: rimuovere il wherevincolo e cambiare l'ultimo returnda return nulla return default(T). In questo modo è possibile restituire qualsiasi tipo desiderato.

A proposito, puoi evitare l'uso iscambiando la tua ifdichiarazione in if (columnValue != DBNull.Value).


4
Questa soluzione non funziona, poiché esiste una differenza logica tra NULL e 0
Greg Dean,

15
Funziona se il tipo che passa è int ?. Restituirà NULL, proprio come vuole. Se passa int come tipo, restituirà 0 poiché un int non può essere NULL. Oltre al fatto che l'ho provato e funziona perfettamente.
Robert C. Barth,

2
Questa è la risposta più corretta e flessibile. Tuttavia, return defaultè sufficiente (non è necessario (T), il compilatore lo inferirà dal tipo restituito dalla firma).
McGuireV10,

5

Disclaimer: questa risposta funziona, ma è intesa solo a scopo educativo. :) La soluzione di James Jones è probabilmente la migliore qui e sicuramente quella con cui vorrei andare.

La dynamicparola chiave di C # 4.0 rende questo ancora più semplice, se meno sicuro:

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

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

Ora non è necessario il suggerimento di tipo esplicito su RHS:

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

In effetti, non ne hai nemmeno bisogno!

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

value sarà ora un int, una stringa o qualunque tipo sia stato restituito dal DB.

L'unico problema è che questo non ti impedisce di utilizzare tipi non nullable su LHS, nel qual caso otterrai un'eccezione di runtime piuttosto brutta come:

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

Come per tutto il codice che utilizza dynamic: caveat coder.


4

Dovevo solo fare qualcosa di incredibile simile a questo. Il mio codice:

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

3

Penso che tu voglia gestire i tipi di riferimento e i tipi di struttura. Lo uso per convertire stringhe di elementi XML in un tipo più tipizzato. È possibile rimuovere nullAlternative con reflection. Il formatprovider è di gestire la cultura dipendente "." o ',' separatore in es. decimali o ints e doppi. Questo potrebbe funzionare:

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

Puoi usarlo in questo modo:

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

2

Questo potrebbe essere un thread morto, ma tendo a usare quanto segue:

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

1
"Il tipo 'T' deve essere un tipo di valore non annullabile per poterlo utilizzare come parametro 'T' nel tipo generico o metodo 'Nullable <T>'"
Ian Warburton

1

Ho riscontrato lo stesso problema da solo.

... = reader["myYear"] as int?; funziona ed è pulito.

Funziona con qualsiasi tipo senza problemi. Se il risultato è DBNull, restituisce null poiché la conversione non riesce.


In effetti, probabilmente potresti fare int v=reader["myYear"]??-1;o qualche altro default invece di -1. Tuttavia, questo potrebbe far sorgere problemi se il valore è DBNull...
Nurchi,

1

So che questo è vecchio, ma ecco un'altra soluzione:

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

Ora, non ti importa se Tfosse un valore o un tipo di riferimento. Solo se la funzione restituisce true, si dispone di un valore ragionevole dal database. Uso:

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

Questo approccio è molto simile a int.TryParse("123", out MyInt);


Sarebbe bello se lavorassi alle tue convenzioni di denominazione. Mancano di coerenza. In un posto c'è una variabile senza capitale, poi ce n'è una con. Lo stesso con i parametri dei metodi.
Marino Šimić,

1
Fatto e fatto! Il codice di speranza ora sembra migliore. Bob è tua zia :) Tutto è skookum
nurchi il

0

Più vincoli generici non possono essere combinati in modo OR (meno restrittivo), solo in modo AND (più restrittivo). Ciò significa che un metodo non può gestire entrambi gli scenari. Inoltre, i vincoli generici non possono essere utilizzati per creare una firma univoca per il metodo, quindi è necessario utilizzare 2 nomi di metodi separati.

Tuttavia, è possibile utilizzare i vincoli generici per assicurarsi che i metodi vengano utilizzati correttamente.

Nel mio caso, volevo specificamente che fosse restituito null e mai il valore predefinito di eventuali tipi di valore. GetValueOrDefault = bad. GetValueOrNull = buono.

Ho usato le parole "Null" e "Nullable" per distinguere tra tipi di riferimento e tipi di valore. Ed ecco un esempio di un paio di metodi di estensione che ho scritto che complimenta il metodo FirstOrDefault nella 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

Il modo più breve:

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

ritorno 0per inte nullperint?

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.