Warum sollte man jemals den "in" -Parameter-Modifikator in C # verwenden?


84

Also verstehe ich (glaube ich), was der inParametermodifikator tut. Aber was es tut, scheint ziemlich überflüssig zu sein.

Normalerweise würde ich denken, dass der einzige Grund für die Verwendung von a refdarin besteht, die aufrufende Variable zu ändern, was von ausdrücklich verboten ist in. Die Übergabe als inReferenz scheint also logisch der Übergabe als Wert zu entsprechen.

Gibt es einen Leistungsvorteil? Ich war der Überzeugung, dass ein refParameter auf der Back-End-Seite mindestens die physische Adresse der Variablen kopieren muss, die dieselbe Größe wie jede typische Objektreferenz haben sollte.

Gibt es dann den Vorteil nur bei größeren Strukturen, oder gibt es eine Compileroptimierung hinter den Kulissen, die es anderswo attraktiv macht? Wenn letzteres der Fall ist, warum sollte ich nicht jeden Parameter zu einem machen in?


1
Ja, es gibt einen Leistungsvorteil. refwird verwendet, um Strukturen als Referenz zu übergeben, anstatt sie zu kopieren. inbedeutet, dass die Struktur nicht geändert werden sollte.
Panagiotis Kanavos

@dbc nein, das hat nichts mit interop zu tun
Panagiotis Kanavos


1
Detaillierte Diskussion hier . Beachten Sie die letzte Warnung:It means that you should never pass a non-readonly struct as in parameter.
Panagiotis Kanavos

Ich hätte schwören können, dass dies ein Duplikat war, aber ich kann es nicht mehr finden
JAD

Antworten:


80

in wurde kürzlich in die C # -Sprache eingeführt.

inist eigentlich ein ref readonly. Im Allgemeinen gibt es nur einen Anwendungsfall in, der hilfreich sein kann: Hochleistungs-Apps, die mit vielen großen readonly structs umgehen.

Vorausgesetzt, Sie haben:

readonly struct VeryLarge
{
    public readonly long Value1;   
    public readonly long Value2;

    public long Compute() { }
    // etc
}

und

void Process(in VeryLarge value) { }

In diesem Fall wird die VeryLargeStruktur als Referenz übergeben, ohne dass defensive Kopien erstellt werden, wenn diese Struktur in der ProcessMethode verwendet wird (z. B. beim Aufrufen value.Compute()), und die Unveränderlichkeit der Struktur wird vom Compiler sichergestellt.

Beachten Sie, dass das Übergeben einer nicht schreibgeschützten Datei structmit einem inModifikator dazu führt, dass der Compiler beim Aufrufen der Methoden von struct und beim Zugriff auf Eigenschaften in der obigen Methode eine defensive Kopie erstelltProcess , was sich negativ auf die Leistung auswirkt!

Es gibt einen wirklich guten MSDN-Blogeintrag, den ich sorgfältig lesen sollte.

Wenn Sie mehr über den historischen Hintergrund der inEinführung erfahren möchten, können Sie diese Diskussion im GitHub-Repository der C # -Sprache lesen .

Im Allgemeinen sind sich die meisten Entwickler einig, dass die Einführung von inals Fehler angesehen werden kann. Es ist eine ziemlich exotische Sprachfunktion und kann nur in Fällen mit hoher Leistung nützlich sein.


Ist es Unterdrückungs - Compiler generierte defensive Kopien oder auch nur die Notwendigkeit für Programmierer beseitigen manuell defensive Kopien in Kontexte zu schaffen , die sonst verwenden würden ref, zum Beispiel , indem sie someProc(in thing.someProperty);gegenüber propType myProp = thing.someProperty; someProc(ref myProp);? ähnelt inein C # -Nur-Konzept outoder wurde es dem .NET Framework hinzugefügt?
Supercat

@supercat, Sie können eine Eigenschaft nicht mit übergeben in, da sie ineffektiv refüber ein spezielles Attribut verfügt. Ihr erstes Snippet wird also nicht kompiliert. @VisualMelon, das ist richtig, das defensive Kopieren erfolgt beim Aufrufen von Methoden oder beim Zugriff auf Eigenschaften der Struktur aus der Methode heraus, die die Struktur als Argument erhält.
Dymanoid

@dymanoid Entschuldigung für das Spammen Sie, ich brauchte ungefähr 10 Versuche, um zu verstehen, was Sie geschrieben hatten (und ich glaube nicht, dass das Ihre Schuld ist!)
VisualMelon

4
@dymanoid: Beide inund outsind Konzepte, die von Anfang an im Framework enthalten sein sollten (zusammen mit einem Mittel, mit dem Methoden und Eigenschaften angeben können, ob sie sich ändern this). Ein Compiler könnte zulassen, dass eine Eigenschaft an ein inArgument übergeben wird, indem ein Verweis auf eine temporäre Eigenschaft übergeben wird, die sie enthält. Wenn eine in einer anderen Sprache geschriebene Funktion diese temporäre Funktion ändert, ist die Semantik etwas schwierig, aber das liegt daran, dass das Verhalten der Funktion nicht mit ihrer Signatur übereinstimmt.
Supercat

1
@supercat, bitte eröffnen Sie hier keine Diskussion (ich bin sowieso nicht im .NET Framework-Konzeptteam). In der Antwort wird auf eine lange Diskussion verwiesen, und diese GitHub-Diskussion verweist auf einige andere - interessante Lektüre. Übrigens können Sie Eigenschaften in VB.NET per Ref übergeben - der Compiler erstellt diese Provisorien für Sie (was natürlich zu undurchsichtigen Problemen führen kann).
Dymanoid

41

Das Übergeben als inReferenz scheint logisch dem Übergeben als Wert zu entsprechen.

Richtig.

Gibt es einen Leistungsvorteil?

Ja.

Ich war der Überzeugung, dass ein refParameter auf der Back-End-Seite mindestens die physische Adresse der Variablen kopieren muss, die dieselbe Größe wie jede typische Objektreferenz haben sollte.

Es gibt keine Anforderung, dass eine Referenz auf ein Objekt und eine Referenz auf eine Variable beide dieselbe Größe haben, und es gibt keine Anforderung, dass beide die Größe eines Maschinenworts haben, aber ja, in der Praxis sind beide 32 Bit auf 32 Bitmaschinen und 64-Bit auf 64-Bit-Maschinen.

Was Ihrer Meinung nach die "physische Adresse" damit zu tun hat, ist mir unklar. Unter Windows verwenden wir virtuelle Adressen , keine physischen Adressen im Benutzermoduscode. Unter welchen möglichen Umständen würden Sie sich vorstellen, dass eine physikalische Adresse in einem C # -Programm von Bedeutung ist? Ich bin neugierig zu wissen.

Es ist auch nicht erforderlich, dass eine Referenz jeglicher Art als virtuelle Adresse des Speichers implementiert wird. Referenzen könnten undurchsichtige Handles in GC-Tabellen in einer konformen Implementierung der CLI-Spezifikation sein.

ist der Vorteil nur bei größeren Strukturen?

Das Motivationsszenario für das Feature besteht darin, die Kosten für das Übergeben größerer Strukturen zu senken .

Beachten Sie, dass es keine Garantie gibt, indie ein Programm tatsächlich schneller macht, und dass es Programme langsamer machen kann. Alle Fragen zur Leistung müssen durch empirische Forschung beantwortet werden . Es gibt nur sehr wenige Optimierungen, die immer gewinnen . Dies ist keine "immer gewinnen" -Optimierung.

Gibt es eine Compiler-Optimierung hinter den Kulissen, die es anderswo attraktiv macht?

Der Compiler und die Laufzeit dürfen jede von ihnen gewählte Optimierung vornehmen, wenn dies nicht gegen die Regeln der C # -Spezifikation verstößt. Es gibt meines Wissens noch keine solche Optimierung für inParameter, aber das schließt solche Optimierungen in Zukunft nicht aus.

Warum sollte ich nicht jeden Parameter zu einem In machen?

Angenommen, Sie haben intstattdessen einen in intParameter als Parameter festgelegt. Welche Kosten fallen an?

  • Die Aufrufsite benötigt jetzt eine Variable anstelle eines Werts
  • Die Variable kann nicht registriert werden. Das sorgfältig abgestimmte Registerzuordnungsschema des Jitters hat gerade einen Schraubenschlüssel hineingeworfen.
  • Der Code an der Aufrufstelle ist größer, da er einen Verweis auf die Variable nehmen und diesen auf den Stapel legen muss, während er zuvor einfach den Wert auf den Aufrufstapel übertragen konnte
  • Größerer Code bedeutet, dass einige Kurzsprunganweisungen möglicherweise zu Weitsprunganweisungen geworden sind. Daher ist der Code jetzt wieder größer. Dies hat Auswirkungen auf alle möglichen Dinge. Caches werden früher gefüllt, der Jitter hat mehr Arbeit zu erledigen, der Jitter kann sich dafür entscheiden, bestimmte Optimierungen bei größeren Codegrößen nicht vorzunehmen, und so weiter.
  • Auf der Angerufenen-Site haben wir den Zugriff auf einen Wert auf dem Stapel (oder Register) in eine Indirektion in einen Zeiger umgewandelt. Nun, dieser Zeiger befindet sich höchstwahrscheinlich im Cache, aber wir haben jetzt einen Ein-Befehl-Zugriff auf den Wert in einen Zwei-Befehl-Zugriff umgewandelt.
  • Und so weiter.

Angenommen, es ist ein doubleund Sie ändern es in ein in double. Auch hier kann die Variable jetzt nicht mehr in ein Hochleistungs-Gleitkommaregister eingetragen werden. Dies hat nicht nur Auswirkungen auf die Leistung , sondern kann auch das Programmverhalten ändern! C # darf Float-Arithmetik mit einer Genauigkeit von mehr als 64 Bit ausführen und dies normalerweise nur, wenn die Floats registriert werden können.

Dies ist keine kostenlose Optimierung. Sie müssen die Leistung an den Alternativen messen. Ihre beste Wahl ist es, überhaupt keine großen Strukturen zu erstellen, wie es die Designrichtlinien vorschlagen.


"Unter welchen [...] möglichen Umständen würden Sie sich vorstellen, dass eine physikalische Adresse in einem C # -Programm von Bedeutung ist, [...]." . C # wird normalerweise auf Systemen verwendet, die über speicherabgebildete E / A verfügen, und über Dinge wie das Zurücksetzen von Vektoren usw. Ich denke hier an einfache alte Winterboxen. Mit x86-Prozessoren und VGA-Framebuffern. Okay, normalerweise manipulieren wir diese nicht direkt, aber wir könnten, richtig?
OmarL

5
Ist das Übergeben inwirklich in allen Fällen gleichbedeutend mit dem Übergeben von Wert? Wenn wir haben f(ref T x, in T y)und fmodifizieren x, sollte es die gleiche Änderung wie ybeim Aufrufen von beobachten f(ref a,a). Gleiches gilt auch, wenn fein in T yund ein Delegat benötigt werden, die sich ybeim Aufruf ändern . Ohne inwäre die Semantik anders, da ysich ihr Wert meiner Meinung nach nie ändern würde, da es sich um eine Kopie handeln würde.
Chi

2
Ich vermute, dass das OP auf informelle Weise "physikalische Adresse" bedeutete, als "das Bitmuster, das den Speicherort beschreibt", im Gegensatz zu "was auch immer das Backend verwendet, um zu beschreiben, wo ein Objekt zu finden ist".
Sneftel

@ Wilson: Ich würde gerne ein Beispiel dafür sehen, wovon Sie sprechen. Ich kenne niemanden, der versucht, solche Dinge in C # zu tun. Im Laufe der Jahre wurde über ein "System-C #" gesprochen, bei dem eine übergeordnete verwaltete Sprache zum Schreiben von Systemkomponenten auf niedriger Ebene verwendet werden könnte, aber ich habe solchen Code selbst noch nie gesehen.
Eric Lippert

2
@chi: Das stimmt. Wenn Sie gefährliche Spiele mit variablem Aliasing spielen, können Sie in Schwierigkeiten geraten. Wenn es weh tut, wenn Sie das tun, tun Sie das nicht. Die Absicht von inist es, den Begriff "Read by Reference, Readonly" darzustellen, der logisch dem Übergeben von Werten entspricht, wenn Sie sich vernünftig verhalten .
Eric Lippert

6

Es gibt. Beim Übergeben von a ermöglicht structdas inSchlüsselwort eine Optimierung, bei der der Compiler nur einen Zeiger übergeben muss, ohne dass das Risiko besteht, dass die Methode den Inhalt ändert. Letzteres ist kritisch - es vermeidet einen Kopiervorgang. Bei großen Strukturen kann dies einen großen Unterschied machen.


2
Dies wiederholt nur das, was die Frage bereits sagt, und beantwortet nicht die eigentliche Frage, die sie stellt.
Servy

Nur in schreibgeschützten Strukturen. Andernfalls erstellt der Compiler immer noch eine defensive Kopie
Panagiotis Kanavos

1
Nicht wirklich. Es wird bestätigt, dass eine Leistung vorliegt. Angesichts der Tatsache, dass IN in der Performance erstellt wurde, die durch die Sprache läuft, ist dies der Grund. Es erlaubt eine Optmisierung (bei schreibgeschützten Strukturen).
TomTom

3
@ TomTom Wieder, die die Frage bereits behandelt . Es wird gefragt: "Also, ist der Vorteil nur bei größeren Strukturen, oder gibt es eine Compiler-Optimierung hinter den Kulissen, die es anderswo attraktiv macht? Wenn letzteres der Fall ist, warum sollte ich nicht jeden Parameter zu einem In machen?" Beachten Sie, dass nicht gefragt wird, ob es für größere Strukturen tatsächlich von Vorteil ist oder ob es überhaupt von Vorteil ist. Es wird nur gefragt, ob es für kleinere Strukturen von Vorteil ist (und dann eine Nachverfolgung, wenn ja). Sie haben keine der beiden Fragen beantwortet.
Servy

2

Dies geschieht aufgrund des funktionalen Programmieransatzes. Eines der Hauptprinzipien ist, dass die Funktion keine Nebenwirkungen haben sollte, was bedeutet, dass sie die Werte der Parameter nicht ändern und einen bestimmten Wert zurückgeben sollte. In C # gab es keine Möglichkeit, Strukturen (und Werttypen) zu übergeben, ohne nur durch Referenz kopiert zu werden, wodurch der Wert geändert werden kann. In Swift gibt es einen Hacky-Algorithmus, der struct kopiert (ihre Sammlungen sind übrigens structs), solange die Methode beginnt, ihre Werte zu ändern. Leute, die schnell arbeiten, sind sich nicht aller der Kopien bewusst. Dies ist eine nette C # -Funktion, da sie speichereffizient und explizit ist. Wenn Sie sich ansehen, was es Neues gibtSie werden sehen, dass immer mehr Dinge um Strukturen und Arrays im Stapel herum gemacht werden. Und in Aussage ist nur für diese Funktionen notwendig. In den anderen Antworten werden Einschränkungen erwähnt, die jedoch nicht unbedingt erforderlich sind, um zu verstehen, wohin .net führt.


2

in es ist nur lesbar Referenz in C # 7.2

Dies bedeutet, dass Sie nicht das gesamte Objekt an den Funktionsstapel übergeben, ähnlich wie bei ref, wenn Sie nur einen Verweis auf die Struktur übergeben

Der Versuch, den Wert des Objekts zu ändern, führt jedoch zu einem Compilerfehler.

Auf diese Weise können Sie die Codeleistung optimieren, wenn Sie große Strukturen verwenden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.