Comment puis-je retourner NULL à partir d'une méthode générique en C #?

546

J'ai une méthode générique avec ce code (factice) (oui, je sais que IList a des prédicats, mais mon code n'utilise pas IList mais une autre collection, de toute façon ce n'est pas pertinent pour la question ...)

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

Cela me donne une erreur de construction

"Impossible de convertir null en paramètre de type 'T' car il pourrait s'agir d'un type de valeur. Envisagez d'utiliser plutôt 'default (T)'."

Puis-je éviter cette erreur?

edosoft
la source
Les types de référence nullables (en C # 8) seraient-ils la meilleure solution maintenant? docs.microsoft.com/en-us/dotnet/csharp/nullable-references De retour nullindépendamment du fait que Test Objectou intou char.
Alexander - Rétablir Monica

Réponses:

969

Deux options:

  • Return, default(T)ce qui signifie que vous retournerez nullsi T est un type de référence (ou un type de valeur nullable), 0for int, '\0'for char, etc. ( tableau des valeurs par défaut (référence C #) )
  • Limitez T à être un type de référence avec la where T : classcontrainte, puis revenez nullnormalement
Jon Skeet
la source
3
Que faire si mon type de retour est une énumération et non une classe? Je ne peux pas spécifier T: enum :(
Justin
1
Dans .NET, une énumération est un wrapper très fin (et plutôt étanche) autour d'un type entier. La convention consiste à utiliser zéro pour votre valeur d'énumération "par défaut".
Mike Chamberlain
27
Je pense que le problème avec cela est que si vous utilisez cette méthode générique pour dire, convertissez un objet Database de DbNull en Int et il retourne par défaut (T) où T est un int, il retournera 0. Si ce nombre est en fait significatif, vous transmettriez alors de mauvaises données dans les cas où ce champ était nul. Ou un meilleur exemple serait un DateTime. Si le champ était quelque chose comme "DateClosed" et qu'il était retourné comme null car le compte était toujours ouvert, il serait en fait par défaut (DateTime) au 1/1/0000, ce qui implique que le compte a été fermé avant que les ordinateurs ne soient inventés.
Sinaesthetic
21
@Sinaesthetic: Donc, vous convertiriez en Nullable<int>ou à la Nullable<DateTime>place. Si vous utilisez un type non nullable et devez représenter une valeur nulle, vous demandez simplement des problèmes.
Jon Skeet
1
Je suis d'accord, je voulais juste en parler. Je pense que ce que je fais ressemble plus à MyMethod <T> (); supposer qu'il s'agit d'un type non nullable et MyMethod <T?> (); supposer qu'il s'agit d'un type nullable. Donc, dans mes scénarios, je pourrais utiliser une variable temporaire pour attraper un null et partir de là.
Sinaesthetic
84
return default(T);
Ricardo Villamil
la source
Ce lien: msdn.microsoft.com/en-us/library/xwth0h0d(VS.80).aspx devrait expliquer pourquoi.
Harper Shelby
1
Bon sang, j'aurais gagné beaucoup de temps si j'avais su ce mot-clé - merci Ricardo!
Ana Betts
1
Je suis surpris que cela n'ait pas obtenu plus de votes, car le mot-clé `` par défaut '' est une solution plus complète, permettant l'utilisation de types non-référence en conjonction avec des types et des structures numériques. Alors que la réponse acceptée résout le problème (et est effectivement utile), elle répond mieux à la façon de restreindre le type de retour aux types nullable / référence.
Steve Jackson du
33

Vous pouvez simplement ajuster vos contraintes:

where T : class

Le retour de null est alors autorisé.

TheSoftwareJedi
la source
Merci. Je ne peux pas choisir 2 réponses comme solution acceptée, donc je choisis John Skeet car sa réponse a deux solutions.
edosoft
@Migol cela dépend de vos besoins. Peut-être que leur projet l'exige IDisposable. Oui, la plupart du temps, ce n'est pas obligatoire. System.Stringne met pas en œuvre IDisposable, par exemple. Le répondeur aurait dû clarifier cela, mais cela ne rend pas la réponse fausse. :)
ahwm
@Migol Je n'ai aucune idée pourquoi j'avais IDisposable là-dedans. Supprimé.
TheSoftwareJedi
13

Ajoutez la contrainte de classe comme première contrainte à votre type générique.

static T FindThing<T>(IList collection, int id) where T : class, IThing, new()
Min
la source
Merci. Je ne peux pas choisir 2 réponses comme solution acceptée, je choisis donc John Skeet car sa réponse a deux solutions.
edosoft
7
  1. Si vous avez un objet, vous devez alors transtyper

    return (T)(object)(employee);
  2. si vous devez retourner null.

    return default(T);
725388
la source
Bonjour utilisateur725388, veuillez vérifier la première option
Jogi Joseph George
7

Voici les deux options que vous pouvez utiliser

return default(T);

ou

where T : class, IThing
 return null;
Jaydeep Shil
la source
6

Votre autre option serait d'ajouter ceci à la fin de votre déclaration:

    where T : class
    where T: IList

De cette façon, cela vous permettra de retourner null.

BFree
la source
Si les deux contraintes sont du même type, vous mentionnez le type une fois et utilisez une virgule, comme where T : class, IList. Si vous avez des contraintes sur différents types, vous répétez le jeton where, comme dans where TFoo : class where TBar : IList.
Jeppe Stig Nielsen
3

solution de TheSoftwareJedi fonctionne,

vous pouvez également l'archiver en utilisant un couple de types valeur et 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;
}
devi
la source
1

Prenez la recommandation de l'erreur ... et soit utilisateur default(T)ou new T.

Vous devrez ajouter une comparaison dans votre code pour vous assurer qu'il s'agissait d'une correspondance valide si vous suivez cette route.

Sinon, envisagez potentiellement un paramètre de sortie pour "correspondance trouvée".

Vendeurs Mitchel
la source
1

Voici un exemple de travail pour les valeurs de retour Nullable Enum:

public static TEnum? ParseOptional<TEnum>(this string value) where TEnum : struct
{
    return value == null ? (TEnum?)null : (TEnum) Enum.Parse(typeof(TEnum), value);
}
Luc
la source
Depuis C # 7.3 (mai 2018), vous pouvez améliorer la contrainte sur where TEnum : struct, Enum. Cela garantit qu'un appelant ne fournit pas accidentellement un type de valeur qui n'est pas une énumération (comme un intou un DateTime).
Jeppe Stig Nielsen du
0

Une autre alternative aux 2 réponses présentées ci-dessus. Si vous changez votre type de retour en object, vous pouvez retourner null, tout en castant le retour non nul.

static object FindThing<T>(IList collection, int id)
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return (T) thing;
    }
    return null;  // allowed now
}
Jeson Martajaya
la source
Inconvénient: cela nécessiterait que l'appelant de la méthode convertisse l'objet retourné (dans le cas non nul), ce qui implique la boxe -> moins de performances. Ai-je raison?
Csharpest
0

Par souci d'exhaustivité, il est bon de savoir que vous pouvez également le faire:

return default;

Il renvoie le même que return default(T);

LCIII
la source
0

Pour moi, ça marche comme ça. Où est exactement le problème?

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


}
Mertuarez
la source