En C #, que se passe-t-il lorsque vous appelez une méthode d'extension sur un objet nul?

329

La méthode est-elle appelée avec une valeur nulle ou donne-t-elle une exception de référence nulle?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

Si tel est le cas, je n'aurai jamais besoin de vérifier la valeur null de mon paramètre «this»?

tpower
la source
Sauf bien sûr, vous avez affaire à ASP.NET MVC qui générera cette erreur Cannot perform runtime binding on a null reference.
Mrchief

Réponses:

387

Cela fonctionnera bien (sans exception). Les méthodes d'extension n'utilisent pas d'appels virtuels (c'est-à-dire qu'elles utilisent l'instruction "call" il, pas "callvirt") donc il n'y a pas de vérification nulle à moins que vous ne l'écriviez vous-même dans la méthode d'extension. Ceci est en fait utile dans quelques cas:

public static bool IsNullOrEmpty(this string value)
{
    return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull<T>(this T obj, string parameterName)
        where T : class
{
    if(obj == null) throw new ArgumentNullException(parameterName);
}

etc

Fondamentalement, les appels aux appels statiques sont très littéraux - c.-à-d.

string s = ...
if(s.IsNullOrEmpty()) {...}

devient:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

où il n'y a évidemment pas de contrôle nul.

Marc Gravell
la source
1
Marc, vous parlez d'appels «virtuels» - mais il en va de même pour les appels non virtuels sur les méthodes d'instance. Je pense que le mot «virtuel» ici est déplacé.
Konrad Rudolph
6
@Konrad: Cela dépend du contexte. Le compilateur C # utilise généralement callvirt même pour les méthodes non virtuelles, précisément pour obtenir une vérification nulle.
Jon Skeet
Je faisais référence à la différence entre les instructions call et callvirt il. En un seul montage, j'ai en fait essayé de href les deux pages Opcodes, mais l'éditeur a raté les liens ...
Marc Gravell
2
Je ne vois pas vraiment comment cette utilisation des méthodes d'extension peut être utile. Ce n'est pas parce que cela peut être fait que c'est juste, et comme Binary Worrier l'a mentionné ci-dessous, cela me ressemble plus à une aberration pour le moins.
Piège
3
@Trap: cette fonctionnalité est idéale si vous êtes dans la programmation de style fonctionnel.
Roy Tinker
50

Ajout à la bonne réponse de Marc Gravell.

Vous pouvez obtenir un avertissement du compilateur s'il est évident que l'argument this est nul:

default(string).MyExtension();

Fonctionne bien à l'exécution, mais génère l'avertissement "Expression will always cause a System.NullReferenceException, because the default value of string is null".

Stefan Steinegger
la source
32
Pourquoi avertirait-il "toujours provoquer une System.NullReferenceException". Quand en fait, ça ne le sera jamais?
tpower
46
Heureusement, nous les programmeurs ne nous soucions que des erreurs, pas des avertissements: p
JulianR
7
@JulianR: Oui, certains le font, d'autres non. Dans notre configuration de version, nous traitons les avertissements comme des erreurs. Donc ça ne marche pas.
Stefan Steinegger
8
Merci pour la note; J'obtiendrai ceci dans la base de données de bogues et nous verrons si nous pouvons le corriger pour C # 4.0. (Aucune promesse - car il s'agit d'un cas d'angle irréaliste et simplement d'un avertissement, nous pourrions tenter de le réparer.)
Eric Lippert
3
@Stefan: Puisqu'il s'agit d'un bogue et non d'un "vrai" avertissement, vous pouvez utiliser une instruction #pragma pour supprimer l'avertissement afin que le code passe votre build de version.
Martin RL
25

Comme vous l'avez déjà découvert, puisque les méthodes d'extension sont simplement des méthodes statiques glorifiées, elles seront appelées avec des nullréférences passées, sans NullReferenceExceptionêtre lancées. Mais, puisqu'elles ressemblent à des méthodes d'instance pour l'appelant, elles devraient également se comporter comme telles. Vous devriez alors, la plupart du temps, vérifier le thisparamètre et lever une exception si c'est le cas null. Il est normal de ne pas le faire si la méthode prend explicitement en charge les nullvaleurs et que son nom l'indique dûment, comme dans les exemples ci-dessous:

public static class StringNullExtensions { 
  public static bool IsNullOrEmpty(this string s) { 
    return string.IsNullOrEmpty(s); 
  } 
  public static bool IsNullOrBlank(this string s) { 
    return s == null || s.Trim().Length == 0; 
  } 
}

J'ai également écrit un article à ce sujet il y a quelque temps.

Jordão
la source
3
A voté cela parce qu'il est correct et logique pour moi (et bien écrit), alors que je préfère également l'utilisation décrite en réponse par @Marc Gravell.
qxotk
17

Un null sera transmis à la méthode d'extension.

Si la méthode essaie d'accéder à l'objet sans vérifier qu'il est nul, alors oui, il lèvera une exception.

Un gars ici a écrit les méthodes d'extension "IsNull" et "IsNotNull" qui vérifient si la référence est nulle ou non. Personnellement, je pense que c'est une aberration et n'aurait pas dû voir le jour, mais c'est parfaitement valide c #.

Binaire Worrier
la source
18
En effet, pour moi, c'est comme demander à un cadavre "Êtes-vous vivant" et obtenir une réponse "non". Un cadavre ne peut répondre à aucune question, vous ne devriez pas non plus pouvoir "appeler" une méthode sur un objet nul.
Binary Worrier
14
Je ne suis pas d'accord avec la logique de Binary Worrier, car il est pratique de pouvoir appeler des extensions sans se soucier des références nulles, mais +1 pour la valeur de la comédie d'analogie :-)
Tim Abell
10
En fait, parfois, vous ne savez pas si quelqu'un est mort, alors vous demandez toujours, et la personne pourrait répondre, "non, juste me reposer les yeux fermés"
nurchi
7
Lorsque vous devez enchaîner plusieurs opérations (par exemple 3+), vous pouvez (en supposant qu'il n'y ait pas d'effets secondaires) transformer plusieurs lignes de code de vérification nulle ennuyeux en une ligne élégante avec des méthodes d'extension "null-safe". (Similaire à l'opérateur ".?" Suggéré, mais il est vrai qu'il n'est pas aussi élégant.) Si ce n'est pas évident, une extension est "null-safe" Je préfixe généralement la méthode par "Safe", donc si par exemple c'est une copie- , son nom pourrait être "SafeCopy" et il retournerait null si l'argument était null.
AnorZaken
3
J'ai ri si fort avec @BinaryWorrier répondre hahahaha je me suis vu donner un coup de pied à un corps pour vérifier s'il était mort ou non hahaha Donc dans MON imagination, qui a vérifié si le corps était mort ou non était moi et pas le corps lui-même, la mise en œuvre de le chèque était en moi, le frappant activement pour voir s'il bouge. Donc, un corps ne sait pas s'il est mort ou non, l'OMS vérifie, sait, maintenant vous pouvez dire que vous pouvez "brancher" le corps pour qu'il vous dise s'il est mort ou non et qu'à mon avis, c'est ce que une extension est pour.
Zorkind
7

Comme d'autres l'ont souligné, l'appel d'une méthode d'extension sur une référence null entraîne la nullité de cet argument et rien d'autre de spécial ne se produit. Cela donne lieu à une idée d'utiliser des méthodes d'extension pour écrire des clauses de garde.

Vous pouvez lire cet article pour des exemples: Comment réduire la complexité cyclomatique: Clause de garde La version courte est la suivante:

public static class StringExtensions
{
    public static void AssertNonEmpty(this string value, string paramName)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Value must be a non-empty string.", paramName);
    }
}

Il s'agit de la méthode d'extension de classe de chaîne qui peut être appelée sur une référence nulle:

((string)null).AssertNonEmpty("null");

L'appel fonctionne correctement uniquement parce que le runtime appellera avec succès la méthode d'extension sur la référence null. Ensuite, vous pouvez utiliser cette méthode d'extension pour implémenter des clauses de garde sans syntaxe compliquée:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }
Zoran Horvat
la source
3

La méthode d'extension est statique, donc si vous ne faites rien à ce MyObject, cela ne devrait pas être un problème, un test rapide devrait le vérifier :)

Fredrik Leijon
la source
-1

Il y a peu de règles d'or lorsque vous voulez que votre fichier soit lisible et vertical.

  • celui qui vaut la peine d'être dit d'Eiffel dit que le code spécifique encapsulé dans une méthode devrait fonctionner contre certaines entrées, que le code est réalisable s'il est satisfait à certaines conditions préalables et assure une sortie attendue

Dans votre cas - DesignByContract est cassé ... vous allez effectuer une logique sur une instance nulle.

ruslander
la source