Was passiert in C #, wenn Sie eine Erweiterungsmethode für ein Nullobjekt aufrufen?

328

Wird die Methode mit einem Nullwert aufgerufen oder gibt sie eine Nullreferenzausnahme?

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

Wenn dies der Fall ist, muss ich meinen 'this'-Parameter niemals auf null überprüfen?

tpower
quelle
Es sei denn natürlich, Sie haben es mit ASP.NET MVC zu tun, das diesen Fehler auslöst Cannot perform runtime binding on a null reference.
Mrchief

Antworten:

386

Das wird gut funktionieren (keine Ausnahme). Erweiterungsmethoden verwenden keine virtuellen Aufrufe (dh sie verwenden die Anweisung "call" il, nicht "callvirt"), sodass keine Nullprüfung durchgeführt wird, es sei denn, Sie schreiben sie selbst in die Erweiterungsmethode. Dies ist in einigen Fällen tatsächlich nützlich:

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

usw

Grundsätzlich sind Aufrufe zu statischen Aufrufen sehr wörtlich - dh

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

wird:

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

wo es offensichtlich keine Nullprüfung gibt.

Marc Gravell
quelle
1
Marc, Sie sprechen von "virtuellen" Aufrufen - aber das Gleiche gilt für nicht-virtuelle Aufrufe von Instanzmethoden. Ich denke, das Wort "virtuell" hier ist falsch platziert.
Konrad Rudolph
6
@Konrad: Das hängt vom Kontext ab. Der C # -Compiler verwendet Callvirt normalerweise auch für nicht virtuelle Methoden, um eine Nullprüfung zu erhalten.
Jon Skeet
Ich bezog mich auf den Unterschied zwischen Anruf- und Anrufanweisungen. In einer Bearbeitung habe ich tatsächlich versucht, die beiden Opcodes-Seiten zu aktualisieren, aber der Editor hat die Links
gesperrt
2
Ich sehe nicht ein, wie diese Verwendung von Erweiterungsmethoden wirklich nützlich sein kann. Nur weil es getan werden kann, heißt das nicht, dass es richtig ist, und wie Binary Worrier unten erwähnt, sieht es für mich gelinde gesagt eher wie eine Aberration aus.
Falle
3
@Trap: Diese Funktion ist großartig, wenn Sie sich für funktionale Programmierung interessieren.
Roy Tinker
50

Ergänzung zur richtigen Antwort von Marc Gravell.

Sie könnten eine Warnung vom Compiler erhalten, wenn es offensichtlich ist, dass dieses Argument null ist:

default(string).MyExtension();

Funktioniert gut zur Laufzeit, erzeugt aber die Warnung "Expression will always cause a System.NullReferenceException, because the default value of string is null".

Stefan Steinegger
quelle
32
Warum sollte es "immer eine System.NullReferenceException verursachen" warnen. Wann wird es tatsächlich nie?
tpower
46
Zum Glück kümmern wir Programmierer uns nur um Fehler, nicht um Warnungen: p
JulianR
7
@ JulianR: Ja, manche tun es, manche nicht. In unserer Release-Build-Konfiguration behandeln wir Warnungen als Fehler. Also funktioniert es einfach nicht.
Stefan Steinegger
8
Danke für den Hinweis; Ich werde dies in die Fehlerdatenbank aufnehmen und sehen, ob wir es für C # 4.0 beheben können. (Kein Versprechen - da es sich um einen unrealistischen Eckfall und lediglich um eine Warnung handelt, könnten wir versuchen, ihn zu beheben.)
Eric Lippert
3
@Stefan: Da es sich um einen Fehler und nicht um eine "wahre" Warnung handelt, können Sie die Warnung mit einer # Pragma-Anweisung unterdrücken, damit der Code Ihren Release-Build besteht.
Martin RL
24

Wie Sie bereits festgestellt haben, werden Erweiterungsmethoden, da es sich lediglich um verherrlichte statische Methoden handelt, mit übergebenen nullReferenzen aufgerufen , ohne NullReferenceExceptiondass sie ausgelöst werden. Da sie für den Aufrufer jedoch wie Instanzmethoden aussehen, sollten sie sich auch als solche verhalten . Sie sollten dann meistens den thisParameter überprüfen und eine Ausnahme auslösen, wenn dies der Fall ist null. Es ist in Ordnung, dies nicht zu tun, wenn sich die Methode explizit um nullWerte kümmert und ihr Name dies ordnungsgemäß anzeigt, wie in den folgenden Beispielen:

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

Ich habe vor einiger Zeit auch einen Blog-Beitrag darüber geschrieben.

Jordão
quelle
3
Ich habe dies gewählt, weil es korrekt und für mich sinnvoll (und gut geschrieben) ist, während ich auch die in der Antwort von @Marc Gravell beschriebene Verwendung bevorzuge.
Qxotk
17

Eine Null wird an die Erweiterungsmethode übergeben.

Wenn die Methode versucht, auf das Objekt zuzugreifen, ohne zu überprüfen, ob es null ist, wird eine Ausnahme ausgelöst.

Ein Typ hier hat die Erweiterungsmethoden "IsNull" und "IsNotNull" geschrieben, die überprüfen, ob die Referenz null übergeben wurde oder nicht. Persönlich denke ich, dass dies eine Aberration ist und nicht das Licht der Welt erblicken sollte, aber es ist vollkommen gültig, c #.

Binärer Sorgen
quelle
18
In der Tat ist es für mich, als würde man eine Leiche fragen "Bist du am Leben" und eine Antwort von "Nein" bekommen. Eine Leiche kann keine Frage beantworten, und Sie sollten auch keine Methode für ein Nullobjekt "aufrufen" können.
Binary Worrier
14
Ich bin mit der Logik von Binary Worrier nicht einverstanden, da es praktisch ist, Erweiterungen aufrufen zu können, ohne sich um Null-Refs sorgen zu müssen, aber +1 für den Wert der Analogie-Komödie :-)
Tim Abell
10
Eigentlich weiß man manchmal nicht, ob jemand tot ist, also fragt man immer noch, und die Person könnte antworten: "Nein, nur mit geschlossenen Augen ruhen"
Nurchi
7
Wenn Sie mehrere Vorgänge verketten müssen (z. B. 3+), können Sie (unter der Annahme, dass keine Nebenwirkungen auftreten) mehrere Zeilen langweiligen Nullprüfungscodes in einen elegant verketteten Einzeiler mit "null-sicheren" Erweiterungsmethoden verwandeln. (Ähnlich wie der vorgeschlagene Operator ".?" - aber zugegebenermaßen nicht so elegant.) Wenn es nicht offensichtlich ist, dass eine Erweiterung "null-sicher" ist, stelle ich der Methode normalerweise "Sicher" voran, wenn es sich also beispielsweise um eine Kopie handelt. Methode, sein Name könnte "SafeCopy" sein, und es würde null zurückgeben, wenn das Argument null wäre.
AnorZaken
3
Ich habe so hart mit der Antwort von @BinaryWorrier gelacht, hahahaha, ich habe gesehen, wie ich gegen einen Körper getreten habe, um zu überprüfen, ob er tot ist oder nicht, hahaha Scheck war in mir und trat aktiv darauf, um zu sehen, ob es sich bewegt. Ein Körper weiß also nicht, ob er tot ist oder nicht, die WHO prüft, weiß, jetzt könnte man argumentieren, dass man sich in den Körper "einstecken" könnte, um ihm zu sagen, ob er tot ist oder nicht, und das ist meiner Meinung nach was eine Erweiterung ist für.
Zorkind
7

Wie andere betonten, führt das Aufrufen einer Erweiterungsmethode für die Nullreferenz dazu, dass dieses Argument null ist und nichts anderes Besonderes passiert. Dies lässt die Idee aufkommen, Erweiterungsmethoden zum Schreiben von Schutzklauseln zu verwenden.

In diesem Artikel finden Sie Beispiele: So reduzieren Sie die zyklomatische Komplexität: Schutzklausel Die Kurzversion lautet wie folgt:

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

Dies ist die String-Klassenerweiterungsmethode, die bei einer Nullreferenz aufgerufen werden kann:

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

Der Aufruf funktioniert nur, weil die Laufzeit die Erweiterungsmethode bei Nullreferenz erfolgreich aufruft. Dann können Sie diese Erweiterungsmethode verwenden, um Schutzklauseln ohne unübersichtliche Syntax zu implementieren:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

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

        ...

    }
Zoran Horvat
quelle
3

Die Erweiterungsmethode ist statisch. Wenn Sie also an diesem MyObject nichts ändern, sollte dies kein Problem sein. Ein schneller Test sollte dies überprüfen :)

Fredrik Leijon
quelle
-1

Es gibt nur wenige goldene Regeln, wenn Sie lesbar und vertikal sein möchten.

  • Eine erwähnenswerte Aussage von Eiffel besagt, dass der in eine Methode eingekapselte spezifische Code gegen einige Eingaben funktionieren sollte. Dieser Code ist funktionsfähig, wenn einige Voraussetzungen erfüllt sind und eine erwartete Ausgabe sichergestellt ist

In Ihrem Fall - DesignByContract ist fehlerhaft ... Sie werden eine Logik für eine Nullinstanz ausführen.

Ruslander
quelle