2014-07-03 17 views
9

ich durch What's the strangest corner case you've seen in C# or .NET? suchen, und dieser Code gemacht denken, mich ein wenig:Warum funktioniert das? Ausführen von Verfahren von IL ohne Beispiel

public class Program 
{ 
    delegate void HelloDelegate(Strange bar); 

    [STAThread()] 
    public static void Main(string[] args) 
    { 
     Strange bar = null; 
     var hello = new DynamicMethod("ThisIsNull", 
      typeof(void), new[] { typeof(Strange) }, 
     typeof(Strange).Module); 
     ILGenerator il = hello.GetILGenerator(256); 
     il.Emit(OpCodes.Ldarg_0); 
     var foo = typeof(Strange).GetMethod("Foo"); 
     il.Emit(OpCodes.Call, foo); 
     il.Emit(OpCodes.Ret); 
     var print = (HelloDelegate)hello.CreateDelegate(typeof(HelloDelegate)); 
     print(bar); 
     Console.ReadLine(); 
    } 

    internal sealed class Strange 
    { 
     public void Foo() 
     { 
      Console.WriteLine(this == null); 
     } 
    } 
} 

Ich verstehe, was der Code tut, aber ich verstehe nicht, warum es funktioniert. Ist das nicht so, null.Foo() zu tun? Es funktioniert so, als ob Foo() statisch ist, und dies wird stattdessen aufgerufen: Strange.Foo();.

Würden Sie mir bitte sagen, was ich vermisse?

Antwort

12

this ist als normaler Parameter implementiert; Ihr Code übergibt einfach null als diesen Parameter.

Der einzige Grund, dass dies in der Regel ein NullReferenceException werfen würde, ist, dass Methoden sind in der Regel den CallVirt Befehl aufgerufen verwenden, die auf dem this Parameter ein VTable-Lookup funktioniert und werfen wenn es null ist.

Wenn Sie call verwenden, wird die Methode auch dann einwandfrei ausgeführt, wenn this null ist, obwohl die Methode selbst wahrscheinlich später werfen wird.

+1

Das ist sehr, sehr interessant! Vielen Dank für Ihre Antwort :) Mir war der Unterschied zwischen 'OpCodes.Callvirt' und' OpCodes.Call' nicht bewusst. Eine kleine Suche führte mich dazu: http://blogs.msdn.com/b/ericgu/archive/2008/07/02/why-does-c-always-use-callvirt.aspx Und zwar im Code oben, ändert 'il.Emit (OpCodes.Call, foo);' zu 'il.Emit (OpCodes.Callvirt, foo);' löst eine Ausnahme aus. Nochmals vielen Dank! – user3439065

+1

+1. [Warum erzeugt der C# -Compiler einen Methodenaufruf, um die BaseClass-Methode in IL aufzurufen] (http://stackoverflow.com/questions/10219188/why-does-c-sharp-compiler-produce-method-call-to-call-baseclass- method-in-il? rq = 1) diskutiert einige Gründe für die Verwendung von 'callvirt' in ein wenig mehr Details, wenn man interessiert ist. –

+0

Dies ist der Grund, warum der Aufruf des Aufrufs des Compilers zum Implementieren von base.Foo(); -Anrufen technisch eine Spec-Verletzung ist: Die C# -Spezifikation erfordert 'base.Foo();', um 'NullReferenceException' if' zu werfen dies == null'. (Natürlich, wenn jemand wirklich daran interessiert ist, das Problem zu beheben, wird das Problem wahrscheinlich darin bestehen, die Spezifikation zu ändern.) – hvd

Verwandte Themen