Come posso restituire NULL da un metodo generico in C #?


546

Ho un metodo generico con questo codice (fittizio) (sì, sono consapevole che IList ha predicati, ma il mio codice non utilizza IList ma un'altra raccolta, comunque questo è irrilevante per la domanda ...)

static T FindThing<T>(IList collection, int id) where T : IThing, new()
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;  // ERROR: Cannot convert null to type parameter 'T' because it could be a value type. Consider using 'default(T)' instead.
}

Questo mi dà un errore di compilazione

"Impossibile convertire null per digitare il parametro 'T' perché potrebbe essere un tipo di valore. Prendi invece in considerazione l'uso di 'default (T)'."

Posso evitare questo errore?


I tipi di riferimento nullable (in C # 8) sarebbero la soluzione migliore a questo ora? docs.microsoft.com/en-us/dotnet/csharp/nullable-references Restituzione nullindipendentemente dal fatto che Tsia Objecto into char.
Alexander - Ripristina Monica

Risposte:


969

Due opzioni:

  • Restituisce il default(T)che significa che tornerai nullse T è un tipo di riferimento (o un tipo di valore nullable), 0per int, '\0'per char, ecc. ( Tabella dei valori predefiniti (Riferimento C #) )
  • Limitare T in modo che sia un tipo di riferimento con il where T : classvincolo e quindi ritorna nullnormalmente

3
Cosa succede se il mio tipo di ritorno è un enum non una classe? Non posso specificare T: enum :(
Justin il

1
In .NET un enum è un wrapper molto sottile (e piuttosto permeabile) attorno a un tipo intero. La convenzione prevede di utilizzare zero per il valore enum "predefinito".
Mike Chamberlain,

27
Penso che il problema con questo sia che se stai usando questo metodo generico per dire, converti un oggetto Database da DbNull a Int e restituisce il valore predefinito (T) dove T è un int, restituirà 0. Se questo numero è effettivamente significativo, quindi passeresti dati errati nei casi in cui quel campo era nullo. O un esempio migliore sarebbe un DateTime. Se il campo fosse simile a "DateClosed" ed è stato restituito come null perché e l'account è ancora aperto, il valore predefinito (DateTime) sarà 1/1/0000, il che implica che l'account è stato chiuso prima dell'invenzione dei computer.
Sinaestetico il

21
@Sinaesthetic: Quindi ti convertiresti Nullable<int>o Nullable<DateTime>invece. Se usi un tipo non annullabile e devi rappresentare un valore nullo, stai solo chiedendo problemi.
Jon Skeet,

1
Sono d'accordo, volevo solo tirarlo su. Penso che quello che ho fatto sia più simile a MyMethod <T> (); supporre che sia un tipo non annullabile e MyMethod <T?> (); supporre che sia un tipo nullable. Quindi nei miei scenari, potrei usare una variabile temp per catturare un null e andare da lì.
Sinaestetico il

84
return default(T);


1
Dannazione, avrei risparmiato molto tempo se avessi saputo di questa parola chiave - grazie Ricardo!
Ana Betts,

1
Sono sorpreso che questo non abbia ottenuto voti più alti in quanto la parola chiave "predefinita" è una soluzione più completa, che consente l'uso di tipi non di riferimento in combinazione con tipi numerici e strutture. Mentre la risposta accettata risolve il problema (e in effetti è utile), risponde meglio come limitare il tipo restituito ai tipi nullable / di riferimento.
Steve Jackson,

33

Puoi semplicemente regolare i tuoi vincoli:

where T : class

Quindi è consentito restituire null.


Grazie. Non posso scegliere 2 risposte come soluzione accettata, quindi scelgo John Skeet perché la sua risposta ha due soluzioni.
edosoft,

@Migol dipende dalle tue esigenze. Forse il loro progetto lo richiede IDisposable. Sì, il più delle volte non deve essere. System.Stringnon implementa IDisposable, per esempio. Il rispondente avrebbe dovuto chiarirlo, ma ciò non rende la risposta sbagliata. :)
ahwm,

@Migol Non ho idea del perché avessi IDisposable lì dentro. Rimosso.
TheSoftwareJedi

13

Aggiungi il vincolo di classe come primo vincolo al tuo tipo generico.

static T FindThing<T>(IList collection, int id) where T : class, IThing, new()

Grazie. Non posso scegliere 2 risposte come soluzione accettata, quindi scelgo John Skeet perché la sua risposta ha due soluzioni.
edosoft,

7
  1. Se hai un oggetto, allora devi digitare

    return (T)(object)(employee);
  2. se è necessario restituire null.

    return default(T);

Salve user725388, si prega di verificare la prima opzione
Jogi Joseph George

7

Di seguito sono le due opzioni che è possibile utilizzare

return default(T);

o

where T : class, IThing
 return null;

6

L'altra opzione sarebbe quella di aggiungere questo alla fine della dichiarazione:

    where T : class
    where T: IList

In questo modo ti consentirà di restituire null.


Se entrambi i vincoli sono dello stesso tipo, devi menzionare il tipo una volta e usare una virgola, come where T : class, IList. Se hai vincoli per tipi diversi, ripeti il ​​token where, come in where TFoo : class where TBar : IList.
Jeppe Stig Nielsen il

3

soluzione di TheSoftwareJedi funziona,

inoltre puoi archiviarlo usando coppia di valori e tipi nullable:

static T? FindThing<T>(IList collection, int id) where T : struct, IThing
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;
}

1

Prendi la raccomandazione dell'errore ... e l'utente default(T)o new T.

Dovrai aggiungere un confronto nel tuo codice per assicurarti che fosse una corrispondenza valida se segui quel percorso.

Altrimenti, considera potenzialmente un parametro di output per "match found".


1

Ecco un esempio funzionante per i valori di ritorno di Numble Enum:

public static TEnum? ParseOptional<TEnum>(this string value) where TEnum : struct
{
    return value == null ? (TEnum?)null : (TEnum) Enum.Parse(typeof(TEnum), value);
}

Da C # 7.3 (maggio 2018), puoi migliorare il vincolo a where TEnum : struct, Enum. Ciò garantisce che un chiamante non fornisca accidentalmente un tipo di valore che non è un enum (come un into un DateTime).
Jeppe Stig Nielsen il

0

Un'altra alternativa a 2 risposte presentate sopra. Se si modifica il tipo restituito in object, è possibile restituire null, mentre allo stesso tempo eseguire il cast di ritorno non nullo.

static object FindThing<T>(IList collection, int id)
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return (T) thing;
    }
    return null;  // allowed now
}

Unico inconveniente: ciò richiederebbe al chiamante del metodo di lanciare l'oggetto restituito (in caso non nullo), il che implica boxe -> minori prestazioni. Ho ragione?
Csharpest,

0

Per completezza, è bene sapere che potresti fare anche questo:

return default;

Restituisce lo stesso di return default(T);


0

Per me funziona così com'è. Dov'è esattamente il problema?

public static T FindThing<T>(this IList collection, int id) where T : IThing, new()
{
    foreach (T thing in collection)
    {
        if (thing.Id == id)
            return thing;
        }
    }

    return null; //work
    return (T)null; //work
    return null as T; //work
    return default(T); //work


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