Frage Ist eine statische Funktion äquivalent zu einem statischen Func-Member in C #?


Es sieht aus wie ein statische Methode ist das gleiche wie statisches Func-Feld. Fehle ich etwas oder sind sie im Wesentlichen austauschbar (gleicher Fußabdruck usw.)?

Eine statische Eigenschaft endet genauso wie die anderen beiden Beispiele, außer dass sie den (minimalen) Overhead des "get" -Accessors enthält.

Vielleicht ein bisschen sinnlos und Nabelschau zu fragen ... aber ich mag es zu verstehen, was "unter der Decke" passiert, auch wenn es nicht unmittelbar relevant ist.

Um sicher zu gehen: Ich werde nicht alle meine statischen Methoden auf Lambda-Ausdrücke umstellen (und meine Kollegen verrückt machen). Aber es könnte ein vernünftiges Szenario geben, in dem eine statische Variable sinnvoller ist als das Schreiben einer Methode. Oder vielleicht das Gegenteil: jemanden dazu überreden, anstelle von Lambda-Ausdrücken statische Methoden zu verwenden, um den Code lesbarer zu machen oder was auch immer

Außerdem bin ich neugierig, ob es eine bessere Möglichkeit gibt, diese Art von Fragen zu untersuchen

Mein Test

Ich habe dieses einfache Beispiel in LINQPad (v4.57.02, "compile with / optimieren +" aktiviert):

void Main()
{
    var hold = "this is the thing. it returns true";
    var held = "this is the one that returns false";

    Console.WriteLine(One.Check(hold));
    Console.WriteLine(One.Check(held));

    Console.WriteLine(Two.Check(hold));
    Console.WriteLine(Two.Check(held));
}

// Define other methods and classes here
class One
{
    public static bool Check(string value)
    {
        return value != null && value.Contains('.');
    }
}

class Two
{
    public static Func<string, bool> Check = v => v != null && v.Contains('.');
}

... und es erzeugte die gleiche IL für beide. Im Wesentlichen:

XXX.Check:
IL_0000:  ldarg.0     
IL_0001:  brfalse.s   IL_000C
IL_0003:  ldarg.0     
IL_0004:  ldc.i4.s    2E 
IL_0006:  call        System.Linq.Enumerable.Contains
IL_000B:  ret         
IL_000C:  ldc.i4.0    
IL_000D:  ret         

XXX..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  ret

10
2018-01-26 22:59


Ursprung


Antworten:


Ich kann mir keinen Fall vorstellen, wo es einen Unterschied im Code-Gen für die Methode (die explizit geschriebene Methode oder den Lambda-Körper) geben würde. Im C # -Compiler gibt es möglicherweise einen seltsamen (oder by-design) Eckfall, aber das ist nicht unbedingt notwendig. Solche Eckfälle können auftreten, weil Lambdas in Verfahren abgesenkt werden müssen, bevor eine Baugruppe emittiert wird. Diese Absenkungsphase könnte (hoffentlich folgenlose) Veränderungen bewirken.

Der wichtigste Punkt, der über Lambda-Code-Gen zu beachten ist, ist, dass Lambdas über Variablen schließen können. Dies erfordert, dass die generierte Methode eine Instanzmethode für eine generierte Klasse wird. Hier wird die Form der Lambdas, die du schreibst, niemals dazu führen.

In neueren C # -Compiler-Versionen ist die generierte Methode eine Instanzmethode für einen Dummy-Typ. Dadurch werden Delegat-Aufrufe schneller (was nicht intuitiv ist, da in diesem Fall mehr Argumente schneller sind).

Das Aufrufen einer solchen "falschen" statischen Methode ist ein Delegat-Aufruf, und der .NET-JIT verfügt nicht über die Möglichkeit, diese zu optimieren. (Zum Beispiel kann Runtime die Call-Site auf ein bekanntes Delegate-Ziel spezialisieren. Die JVM tut dies für virtuelle Aufrufe. Es ist inline durch virtuelle Aufrufe. Das Hotspot-JIT ist sehr weit fortgeschritten.) Dies bedeutet, dass Sie indirekten Aufruf-Overhead haben loses Inlining und auch alle nachfolgenden Optimierungen.

Wenn nicht notwendig, sollte dies nie gemacht werden. Es kann nützlich sein, wenn Sie zur Laufzeit verschiedene Methoden anschließen möchten. Oder, vielleicht diese statische Func Variablen können als Cache dienen. Oder Sie können sie im Debugger neu verkabeln.

Das get Der Zugriff auf eine solche statische Eigenschaft sollte genau null sein, da kleine Methoden zuverlässig inline sind.

Ein weiterer kleiner Leistungsnachteil ist die erhöhte Lade- und Initialisierungszeit. Auch, mehr Gegenstände, die herumhängen, verlangsamen sich allezukünftige G2-Kollektionen.


1
2018-01-27 00:34



Nun, die offensichtliche Antwort ist, dass nein, sind sie nicht.

Two.Check = v => { throw new Exception(); };

Der IL-Code, der für die Methoden generiert wird, ist derselbe, da Sie immer noch die gleiche Implementierung definieren, aber die Art und Weise, wie sie referenziert wird, ändern.

Selbst wenn Check wurden readonly, du könntest das immer noch tun:

class Two
{
    public static readonly Func<string, bool> Check;
    static Two()
    {
        if (Helper.IsBlueMoon && Helper.IsFirst)
        {
            Check = v => { throw new Exception(); };
        } else {
            Check = v => v != null && v.Contains('.');
        }
    }
}

Eine andere wichtige Sache zu beachten ist, dass Argumente nicht benannt werden, wenn Sie verwenden Func<string, bool>, und Sie können nicht verwenden Kommentare zur XML-Dokumentation 


1
2018-01-26 23:05