Vorteile der Verwendung von const anstelle von Variablen in Methoden

76

Immer wenn ich lokale Variablen in einer Methode habe, schlägt ReSharper vor, sie in Konstanten zu konvertieren:

// instead of this:
var s = "some string";
var flags = BindingFlags.Public | BindingFlags.Instance;

// ReSharper suggest to use this:
const string s = "some string";
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;

Da dies wirklich konstante Werte (und keine Variablen) sind, verstehe ich, dass ReSharper vorschlägt, sie in const zu ändern.

Aber abgesehen davon gibt es einen anderen Vorteil bei der Verwendung von const (z. B. bessere Leistung), der die Verwendung const BindingFlagsanstelle des handlichen und lesbaren varSchlüsselworts rechtfertigt ?

Übrigens: Ich habe hier gerade eine ähnliche Frage gefunden: Resharper schlägt mir immer vor, const string anstelle von string zu erstellen , aber ich denke, es geht mehr um Felder einer Klasse, in denen es um lokale Variablen / consts geht.

M4N
quelle

Antworten:

87

Der Compiler gibt einen Fehler aus, wenn Sie versuchen, einer Konstanten einen Wert zuzuweisen, wodurch Sie möglicherweise daran gehindert werden, ihn versehentlich zu ändern.

Außerdem bietet die Verwendung von Konstanten im Vergleich zu Variablen normalerweise einen geringen Leistungsvorteil. Das hat mit der Art und Weise zu tun , sie zu dem MSIL kompiliert werden, pro diesem MSDN Magazin Q & A :

Wo immer myInt im Code referenziert ist, lädt die MSIL nur den konstanten Wert, der fest in die MSIL codiert ist, anstatt "ldloc.0" ausführen zu müssen, um den Wert aus der Variablen abzurufen. Daher bietet die Verwendung von Konstanten normalerweise einen geringen Leistungs- und Speichervorteil. Um sie jedoch verwenden zu können, muss der Wert der Variablen zur Kompilierungszeit vorhanden sein. Bei Verweisen auf diese Konstante zur Kompilierungszeit, auch wenn sie sich in einer anderen Assembly befinden, wird diese Ersetzung vorgenommen.

Konstanten sind sicherlich ein nützliches Werkzeug, wenn Sie den Wert zur Kompilierungszeit kennen. Wenn Sie dies nicht tun, aber sicherstellen möchten, dass Ihre Variable nur einmal festgelegt wird, können Sie das Schlüsselwort readonly in C # (das in MSIL anfänglich zugeordnet ist) verwenden, um anzugeben, dass der Wert der Variablen nur im Konstruktor festgelegt werden kann. Danach ist es ein Fehler, es zu ändern. Dies wird häufig verwendet, wenn ein Feld zur Bestimmung der Identität einer Klasse beiträgt, und wird häufig einem Konstruktorparameter gleichgesetzt.

landoncz
quelle
6
Für lokale Variablen gibt es bei Verwendung keinen Leistungsvorteil const. Es gibt dem Compiler keine zusätzlichen Informationen. Überprüfen Sie diese Antwort für weitere Details.
Drew Noakes
Zumindest für Mono ist dies nicht immer der Fall. Verifiziert mit Mono 5.14.0.177. Lokale Konstanten verursachen bei Verwendung von Konstanten zur Berechnung eines Wertes (z. B. Konstante int a = 10; Konstante int b = a + 20;) unterschiedliche Anweisungen. ldc.r4 110 anstelle von ldc.i4.s und anderen Operationen, um die Werte tatsächlich zu berechnen. (getestet mit Debug als Ziel, finde es immer noch relevant, insbesondere für Hochleistungscode; für niedrige Leistung ist der Unterschied möglicherweise nicht so relevant)
Daniel Bişar
2
@SACO Wenn Sie sich Sorgen um Perf machen, sollten Sie die Release-Builds überprüfen. Selbst wenn der Mono-Compiler die Additionsoperation ausgibt, würde die JIT wahrscheinlich eine grundlegende konstante Falte auf den Maschinencode anwenden. Roslyn in C # 7.3 macht das Richtige .
Drew Noakes
@ Draw Noakes schön! Und ein großartiger Link, den Sie geteilt haben.
Daniel Bişar
@SACO Schauen Sie sich auch die JIT Asm-Ansicht an .
Drew Noakes
28

tl; dr für lokale Variablen mit Literalwerten constmacht überhaupt keinen Unterschied.


Ihre Unterscheidung von "Insider-Methoden" ist sehr wichtig. Schauen wir es uns an und vergleichen es dann mit constFeldern.

Const lokale Variablen

Der einzige Vorteil einer constlokalen Variablen besteht darin, dass der Wert nicht neu zugewiesen werden kann.

Allerdings constbeschränkt sich auf primitive Typen ( int, double, ...) und string, die ihre Anwendbarkeit begrenzt.

Exkurs: Es gibt Vorschläge für den C # -Compiler, ein allgemeineres Konzept von "schreibgeschützten" Einheimischen ( hier ) zu ermöglichen, das diesen Vorteil auf andere Szenarien ausweiten würde. Sie werden wahrscheinlich nicht als gedacht werden constaber, und wahrscheinlich ein anderes Schlüsselwort für solche Erklärungen (dh haben würden letoder readonly varoder so ähnlich).

Betrachten Sie diese beiden Methoden:

private static string LocalVarString()
{
    var s = "hello";
    return s;
}

private static string LocalConstString()
{
    const string s = "hello";
    return s;
}

Im eingebauten ReleaseModus sehen wir die folgende (gekürzte) IL:

.method private hidebysig static string LocalVarString() cil managed 
{
    ldstr        "hello"
    ret          
}

.method private hidebysig static string LocalConstString() cil managed 
{
    ldstr        "hello"
    ret          
}

Wie Sie sehen können, produzieren beide genau die gleiche IL. Ob das Lokal sist constoder nicht, hat keinen Einfluss.

Gleiches gilt für primitive Typen. Hier ist ein Beispiel mit int:

private static int LocalVarInt()
{
    var i = 1234;
    return i;
}

private static int LocalConstInt()
{
    const int i = 1234;
    return i;
}

Und wieder die IL:

.method private hidebysig static int32 LocalVarInt() cil managed
{
    ldc.i4       1234
    ret          
}

.method private hidebysig static int32 LocalConstInt() cil managed
{
    ldc.i4       1234
    ret     
}

Wir sehen also wieder keinen Unterschied. Hier kann es keinen Leistungs- oder Speicherunterschied geben. Der einzige Unterschied besteht darin, dass der Entwickler das Symbol nicht neu zuweisen kann.

Const-Felder

Der Vergleich eines constFeldes mit einem variablen Feld ist unterschiedlich. Ein nicht konstantes Feld muss zur Laufzeit gelesen werden. Am Ende haben Sie also IL wie folgt:

// Load a const field
ldc.i4       1234

// Load a non-const field
ldsfld       int32 MyProject.MyClass::_myInt

Es ist klar zu sehen, wie dies zu einem Leistungsunterschied führen kann, vorausgesetzt, die JIT kann selbst keinen konstanten Wert inline setzen.

Ein weiterer wichtiger Unterschied besteht in öffentlichen Konstantenfeldern, die von Assemblys gemeinsam genutzt werden. Wenn eine Assembly ein const-Feld verfügbar macht und eine andere es verwendet, wird der tatsächliche Wert dieses Felds zur Kompilierungszeit kopiert . Dies bedeutet, dass der alte (und möglicherweise falsche) Wert verwendet wird, wenn die Assembly, die das const-Feld enthält, aktualisiert wird, die using-Assembly jedoch nicht neu kompiliert wird.

Const-Ausdrücke

Betrachten Sie diese beiden Erklärungen:

const int i = 1 + 2;
int i = 1 + 2;

Für das constFormular muss die Addition zur Kompilierungszeit berechnet werden, dh die Nummer 3 wird in der IL gespeichert.

Für die Nichtform constkann der Compiler die Additionsoperation in der IL ausgeben, obwohl die JIT mit ziemlicher Sicherheit eine grundlegende Optimierung der konstanten Faltung anwenden würde, so dass der generierte Maschinencode identisch wäre.

Der C # 7.3-Compiler gibt den ldc.i4.3Opcode für beide oben genannten Ausdrücke aus.

Drew Noakes
quelle
Ich kann Ihrer Aussage nicht zustimmen, dass "für lokale Variablen mit Literalwerten const überhaupt keinen Unterschied macht". Sie sind sinnvoll, wenn sie in einer Methode mit frühen Rückgaben und bedingten Anweisungen verwendet werden. Wenn Sie beispielsweise eine Variable am Anfang Ihrer Methode deklarieren und nur innerhalb von if () verwenden, nimmt const kein Byte aus dem Speicher, bis Sie die if-Anweisung eingeben (während die reguläre Variable ohnehin einige Bytes frisst).
Yury Kozlov
1
Angenommen, Sie haben Mikrooptimierung durchgeführt. Wäre es sinnvoll, Konstanten, die in lokalen Methoden verwendet werden, als Felder innerhalb der Klasse zu definieren und sie dann innerhalb von Methoden zu referenzieren? Ich denke, dies würde auf Kosten der Lesbarkeit gehen und diese Methoden weniger "in sich geschlossen" (gekapselt) machen.
Dan Diplo
1
@ YuryKozlov gibt es überhaupt keinen Unterschied. Schauen Sie sich die IL an.
Drew Noakes
@DanDiplo Wenn es const ist, spielt es keine Rolle, wo Sie es definieren. Der const-Wert wird direkt in der IL ausgedrückt.
Drew Noakes
15

Nach meinem Verständnis existieren Const-Werte zur Laufzeit nicht - dh in Form einer Variablen, die an einem bestimmten Speicherort gespeichert ist - sie werden zur Kompilierungszeit in MSIL-Code eingebettet. Und würde sich somit auf die Leistung auswirken. Mehr über die Laufzeit wäre nicht erforderlich, um auch eine Haushaltsführung (Konvertierungsprüfungen / Speicherbereinigung usw.) durchzuführen, sofern Variablen diese Überprüfungen erfordern.

Noch ein anderer Benutzer
quelle
3
plus 1 für den Hinweis auf Konvertierungsprüfungen / Speicherbereinigung.
Christian Mark
Für den in der ursprünglichen Frage angegebenen Code gibt es keinen Unterschied zum generierten IL und daher keinen Unterschied zum Speicher, zur Überprüfung, zur Speicherbereinigung usw.
Drew Noakes
Konstantenwerte existieren zur Laufzeit nicht, was etwas irreführend ist. Sie existieren, sonst könnten Sie sie nicht verwenden.
Liam
@ Liam - stimme zu, ich habe versucht, es im nächsten Satz zu erklären. "Const-Werte existieren zur Laufzeit nicht - dh in Form einer Variablen, die an einem bestimmten Speicherort gespeichert ist "
YetAnotherUser
4

const ist eine Kompilierungszeitkonstante - das heißt, Ihr gesamter Code, der die const-Variable verwendet, wird so kompiliert, dass er den konstanten Ausdruck enthält, den die const-Variable enthält. Die ausgegebene IL enthält diesen konstanten Wert selbst.

Dies bedeutet, dass der Speicherbedarf für Ihre Methode geringer ist, da für die Konstante zur Laufzeit kein Speicher zugewiesen werden muss.

Glassplitter
quelle
1
Ich glaube nicht, dass die Aussage zur Gedächtnisreduzierung hier wahr ist. constFunktioniert nur mit Grundelementen (die dazu führen, dass IL Konstanten auf den Auswertungsstapel lädt, wie Sie sagen) und Zeichenfolgen (die dieselbe Speichermenge zuweisen, unabhängig davon, ob sie konstant sind oder nicht).
Drew Noakes
1
Hat einige Tests durchgeführt und es gibt keinen Unterschied zur Laufzeit zum Speicher oder irgendetwas anderem. Der generierte Code ist identisch.
Drew Noakes
4

Neben der geringen Leistungsverbesserung erzwingen Sie beim Deklarieren einer Konstante explizit zwei Regeln für sich selbst und andere Entwickler, die Ihren Code verwenden

  1. Ich muss es jetzt mit einem Wert initialisieren. Ich kann es an keiner anderen Stelle tun.
  2. Ich kann seinen Wert nirgendwo ändern.

Im Code dreht sich alles um Lesbarkeit und Kommunikation.

Shrage Smilowitz
quelle
2

Ein const-Wert wird auch von allen Instanzen eines Objekts gemeinsam genutzt. Dies könnte auch zu einer geringeren Speichernutzung führen.

Als Beispiel:

public class NonStatic
{
    int one = 1;
    int two = 2;
    int three = 3;
    int four = 4;
    int five = 5;
    int six = 6;
    int seven = 7;
    int eight = 8;
    int nine = 9;
    int ten = 10;        
}

public class Static
{
    static int one = 1;
    static int two = 2;
    static int three = 3;
    static int four = 4;
    static int five = 5;
    static int six = 6;
    static int seven = 7;
    static int eight = 8;
    static int nine = 9;
    static int ten = 10;
}

Der Speicherverbrauch in .Net ist schwierig, und ich werde nicht vorgeben, die feineren Details zu verstehen. Wenn Sie jedoch eine Liste mit einer Million 'Static' instanziieren, wird wahrscheinlich erheblich weniger Speicher benötigt als wenn Sie dies nicht tun.

    static void Main(string[] args)
    {
        var maxSize = 1000000;
        var items = new List<NonStatic>();
        //var items = new List<Static>();

        for (var i=0;i<maxSize;i++)
        {
            items.Add(new NonStatic());
            //items.Add(new Static());
        }

        Console.WriteLine(System.Diagnostics.Process.GetCurrentProcess().WorkingSet64);
        Console.Read();
    }

Bei Verwendung von 'NonStatic' beträgt der Arbeitssatz 69.398.528 gegenüber nur 32.423.936 bei Verwendung von Static.

Rob P.
quelle
Der einzige Referenztyp, mit dem Sie verwenden können, constist string. Sowohl ein const string ( const string s = "foo";) als auch ein string literal ( var s = "foo";) werden interniert. Hier gibt es also keinen Unterschied zum Speicherverbrauch. Alle anderen konstanten Werte werden als Wert übergeben.
Drew Noakes
@DrewNoakes - Ich bin nicht sicher, ob ich das vollständig verstehe, aber ich habe meine Antwort erweitert. Bitte lassen Sie mich wissen, wenn ich einen Fehler gemacht habe oder Ihren Standpunkt verfehlt habe.
Rob P.
Static unterscheidet sich von const und hat leider nichts mit der ursprünglichen Frage zu tun. Ich denke, Ihre Bearbeitung hat Ihrer Antwort nicht geholfen. Sie können mit const keinen Test über N Elemente durchführen, da Sie am Ende eine einzelne Instanz im Speicher haben, genau wie bei einer Kompilierungszeitzeichenfolge. Beispielsweise wird ein Zeichenfolgenliteral "hello"interniert, sodass zwei Methoden, die "hello"als lokale Variablen definiert werden, dieselbe Referenz verwenden (auf der Grundlage, dass Zeichenfolgen unveränderlich sind). Ist "hello".ToUpper()jedoch ein Laufzeitwert, sodass mehrere Aufrufe mehrere Instanzen erzeugen.
Drew Noakes
Ich habe in dieser Antwort weitere Informationen angegeben .
Drew Noakes
1

Das Schlüsselwort const teilt dem Compiler mit, dass es zur Kompilierungszeit vollständig ausgewertet werden kann. Dies hat einen Leistungs- und Speichervorteil, ist jedoch gering.

Chris Trombley
quelle
1
Tatsächlich gibt es bei einer lokalen Variablen keinen Leistungs- oder Speicherunterschied. Siehe meine Antwort .
Drew Noakes
1

Konstanten in C # stellen einen benannten Speicherort im Speicher bereit , um einen Datenwert zu speichern. Dies bedeutet, dass der Wert der Variablen in der Kompilierungszeit bekannt ist und an einem einzigen Ort gespeichert wird.

Wenn Sie es deklarieren, ist es in der Microsoft Intermediate Language (MSIL) "fest codiert".

Obwohl ein wenig, kann es die Leistung Ihres Codes verbessern. Wenn ich eine Variable deklariere und sie zu einer Konstante machen kann, mache ich das immer. Nicht nur, weil es die Leistung verbessern kann, sondern auch, weil dies die Idee von Konstanten ist. Warum existieren sie sonst?

Reflektor kann in Situationen wie dieser sehr nützlich sein. Versuchen Sie, eine Variable zu deklarieren, und machen Sie sie dann zu einer Konstanten. Sehen Sie, welcher Code in IL generiert wird . Dann müssen Sie nur noch den Unterschied in den Anweisungen erkennen und sehen, was diese Anweisungen bedeuten.

Oscar Mederos
quelle
Ist Ihr zweiter Satz nicht genau das Gegenteil von dem, was @BrokenGlass in seiner Antwort geschrieben hat?
M4N