2013-10-14 14 views
15

Ich erhalte eine unerwartete NullReferenceException, wenn ich diesen Code ausführen, jedoch ohne den fileSystemHelper Parameter (und damit säumige es auf null):Warum funktioniert der Nullkoaleszenzoperator (??) in dieser Situation nicht?

public class GitLog 
    { 
    FileSystemHelper fileSystem; 

    /// <summary> 
    /// Initializes a new instance of the <see cref="GitLog" /> class. 
    /// </summary> 
    /// <param name="pathToWorkingCopy">The path to a Git working copy.</param> 
    /// <param name="fileSystemHelper">A helper class that provides file system services (optional).</param> 
    /// <exception cref="ArgumentException">Thrown if the path is invalid.</exception> 
    /// <exception cref="InvalidOperationException">Thrown if there is no Git repository at the specified path.</exception> 
    public GitLog(string pathToWorkingCopy, FileSystemHelper fileSystemHelper = null) 
     { 
     this.fileSystem = fileSystemHelper ?? new FileSystemHelper(); 
     string fullPath = fileSystem.GetFullPath(pathToWorkingCopy); // ArgumentException if path invalid. 
     if (!fileSystem.DirectoryExists(fullPath)) 
      throw new ArgumentException("The specified working copy directory does not exist."); 
     GitWorkingCopyPath = pathToWorkingCopy; 
     string git = fileSystem.PathCombine(fullPath, ".git"); 
     if (!fileSystem.DirectoryExists(git)) 
      { 
      throw new InvalidOperationException(
       "There does not appear to be a Git repository at the specified location."); 
      } 
     } 

Wenn ich Schritt den Code im Debugger Single, nachdem ich über den Schritt erste Zeile (mit dem ?? Operator) dann fileSystem noch den Wert null hat, wie in diesem Bildschirm schnipp gezeigt (Schritt über die nächste Zeile wirft NullReferenceException): When is null not null?

das ist nicht das, was ich erwartet habe! Ich erwarte, dass der Nullkoaleszenzoperator erkennt, dass der Parameter null ist, und erstellen Sie eine new FileSystemHelper(). Ich habe diesen Code seit Ewigkeiten gesehen und kann nicht sehen, was damit nicht stimmt.

ReSharper wies darauf hin, dass das Feld nur in dieser einen Methode verwendet wird, also möglicherweise in eine lokale Variable umgewandelt werden könnte ... also habe ich das versucht und rate mal was? Es funktionierte. Also, ich habe meine Lösung, aber ich kann nicht für das Leben von mir sehen, warum der obige Code nicht funktioniert. Ich habe das Gefühl, dass ich gerade dabei bin, etwas Interessantes über C# zu lernen, oder ich habe etwas wirklich Dummes getan. Kann jemand sehen, was hier passiert?

+0

Sie sagen bereits, dass 'fileSystemHelper' in den Methodenparametern' null' ist, ich bin mir nicht sicher, aber es könnte etwas damit zu tun haben. Aber andererseits, vermute ich. – Tico

+2

Sind Sie sicher, dass die NRE nicht von * innerhalb * GetFullPath (ignoriert, was die Uhr zeigt) auftritt? Ich sehe nichts mit dem obigen Code, der zu besagtem Verhalten führen würde. – user2864740

+0

OK, nachdem ich VisualStudio beendet habe, um etwas anderes zu tun, habe ich es neu geladen, alles funktioniert jetzt und ich kann das Problem nicht reproduzieren. Ich denke, dies könnte ein seltsames Caching-Problem mit dem ReSharper Unit Test Runner sein. Ich verwende einen einfachen MSpec-Test, um den Code auszuprobieren. ReSharper erstellt eine Shadow-Kopie der Assembly, wenn Unit-Tests ausgeführt werden. Manchmal scheint die Shadow-Kopie manchmal "hängen zu bleiben". Ich habe es schon ein paar Mal gesehen. Am wahrscheinlichsten war, dass ich tatsächlich alten Code ausführte, obwohl ich alles manuell neu erstellt hatte. Es ist die beste Erklärung, die ich habe ... –

Antwort

12

Ich habe es in VS2012 mit dem folgenden Code wiedergegeben:

public void Test() 
{ 
    TestFoo(); 
} 

private Foo _foo; 

private void TestFoo(Foo foo = null) 
{ 
    _foo = foo ?? new Foo(); 
} 

public class Foo 
{ 
} 

Wenn Sie einen Haltepunkt am Ende der TestFoo Methode festgelegt, würden Sie erwarten, dass der _foo variablen Satz zu sehen, aber es wird immer noch zeigen, als Null im Debugger.

Aber wenn Sie dann irgendetwas mit _foo tun, dann erscheint es richtig. Selbst eine einfache Zuordnung wie

_foo = foo ?? new Foo(); 
var f = _foo; 

Wenn Sie durch sie Schritt, werden Sie sehen, dass _foo null zeigt, bis es zu f zugeordnet ist.

Das erinnert mich an verzögertes Ausführungsverhalten, wie mit LINQ, aber ich kann nichts finden, das das bestätigen würde.

Es ist durchaus möglich, dass dies nur eine Eigenart des Debuggers ist. Vielleicht kann jemand mit MSIL-Kenntnissen ein wenig Licht auf das werfen, was unter der Haube geschieht.

Interessant ist auch, dass, wenn Sie die Null-Koaleszenz Operator ersetzen damit gleichwertig ist:

_foo = foo != null ? foo : new Foo(); 

Dann ist es dieses Verhalten nicht zeigen.

Ich bin kein Montage/MSIL Kerl, aber nur einen Blick auf die dissasembly Ausgang zwischen den beiden Versionen nehmen ist interessant:

 _foo = foo ?? new Foo(); 
0000002d mov   rax,qword ptr [rsp+68h] 
00000032 mov   qword ptr [rsp+28h],rax 
00000037 mov   rax,qword ptr [rsp+60h] 
0000003c mov   qword ptr [rsp+30h],rax 
00000041 cmp   qword ptr [rsp+68h],0 
00000047 jne   0000000000000078 
00000049 lea   rcx,[FFFE23B8h] 
00000050 call  000000005F2E8220 
     var f = _foo; 
00000055 mov   qword ptr [rsp+38h],rax 
0000005a mov   rax,qword ptr [rsp+38h] 
0000005f mov   qword ptr [rsp+40h],rax 
00000064 mov   rcx,qword ptr [rsp+40h] 
00000069 call  FFFFFFFFFFFCA000 
0000006e mov   r11,qword ptr [rsp+40h] 
00000073 mov   qword ptr [rsp+28h],r11 
00000078 mov   rcx,qword ptr [rsp+30h] 
0000007d add   rcx,8 
00000081 mov   rdx,qword ptr [rsp+28h] 
00000086 call  000000005F2E72A0 
0000008b mov   rax,qword ptr [rsp+60h] 
00000090 mov   rax,qword ptr [rax+8] 
00000094 mov   qword ptr [rsp+20h],rax 

Vergleichen Sie das mit der inlined-if-Version:

 _foo = foo != null ? foo : new Foo(); 
0000002d mov   rax,qword ptr [rsp+50h] 
00000032 mov   qword ptr [rsp+28h],rax 
00000037 cmp   qword ptr [rsp+58h],0 
0000003d jne   0000000000000066 
0000003f lea   rcx,[FFFE23B8h] 
00000046 call  000000005F2E8220 
0000004b mov   qword ptr [rsp+30h],rax 
00000050 mov   rax,qword ptr [rsp+30h] 
00000055 mov   qword ptr [rsp+38h],rax 
0000005a mov   rcx,qword ptr [rsp+38h] 
0000005f call  FFFFFFFFFFFCA000 
00000064 jmp   0000000000000070 
00000066 mov   rax,qword ptr [rsp+58h] 
0000006b mov   qword ptr [rsp+38h],rax 
00000070 nop 
00000071 mov   rcx,qword ptr [rsp+28h] 
00000076 add   rcx,8 
0000007a mov   rdx,qword ptr [rsp+38h] 
0000007f call  000000005F2E72A0 
     var f = _foo; 
00000084 mov   rax,qword ptr [rsp+50h] 
00000089 mov   rax,qword ptr [rax+8] 
0000008d mov   qword ptr [rsp+20h],rax 

Darauf basierend denke ich, dass es eine Art verzögerte Ausführung gibt. Die Zuweisungsanweisung im zweiten Beispiel ist im Vergleich zum ersten Beispiel sehr klein.

+3

Dies scheint ein Problem mit dem 64-Bit-Debugger zu sein. Das Erstellen und Kompilieren von Targeting "Any CPU" zeigt dieses Verhalten. Ändern Sie es zu Ziel x86, dann ist es kein Problem mehr. –

+3

Ich denke, ich weiß, was das Problem ist ... die Zeilennummern, die von VS in dem 64-Bit-Build generiert werden, sind falsch. Die Zeilennummer, die für die Linie erzeugt wird, die der Null-Koaleszenzlinie folgt, ist tatsächlich "in der Mitte" der Nullkoaleszenzanweisung. Der Debugger bricht also diese Zeile, aber die vorherige Anweisung ist noch nicht vollständig abgeschlossen. Wenn Sie diese Zeile überspringen, wird diese Anweisung beendet. Sie können den Punkt sehen, an dem die Elementvariable gesetzt wird, wenn Sie die Disassemblierung durchgehen. –

+0

@JeffMercado - Bestätigt. Dies geschieht nur mit "Any CPU" oder "x64". Funktioniert gut mit "x86". –

1

Jemand anderes hatte das gleiche Problem in this question. Interessanterweise verwendet es auch ein this._field = expression ?? new ClassName(); Format. Es könnte eine Art Problem mit dem Debugger sein, da das Schreiben des Wertes die richtigen Ergebnisse für sie erzeugt.

Versuchen Sie, Debug/Log-Code hinzuzufügen, um den Wert des Feldes nach der Zuweisung anzuzeigen, um Verrücktheiten im angehängten Debugger zu beseitigen.