Was ist der Unterschied zwischen den Schlüsselwörtern "ref" und "out"?


891

Ich erstelle eine Funktion, bei der ich ein Objekt übergeben muss, damit es von der Funktion geändert werden kann. Was ist der Unterschied zwischen:

public void myFunction(ref MyClass someClass)

und

public void myFunction(out MyClass someClass)

Welches soll ich verwenden und warum?


69
Sie: Ich muss ein Objekt übergeben, damit es geändert werden kann. Es sieht so aus, als MyClasswäre es ein classTyp, dh ein Referenztyp. In diesem Fall kann das Objekt , das Sie passieren die modifiziert werden , myFunctionsogar ohne ref/ outSchlüsselwort. myFunctionerhält eine neue Referenz, die auf dasselbe Objekt verweist , und kann dasselbe Objekt beliebig ändern. Der Unterschied die refKeyword machen würde, wäre, dass myFunctionder empfangene gleichen Bezug auf das gleiche Objekt. Dies wäre nur dann wichtig, wenn myFunctionder Verweis so geändert würde, dass er auf ein anderes Objekt verweist .
Jeppe Stig Nielsen

3
Ich bin verwirrt über die Menge verwirrender Antworten hier, wenn @ AnthonyKolesov's ganz perfekt ist.
o0 '.

Das Deklarieren einer out-Methode ist nützlich, wenn eine Methode mehrere Werte zurückgeben soll. Ein Argument kann null zugewiesen werden. Dadurch können Methoden optional Werte zurückgeben.
Jewgraf Andrejewitsch Schiwago

Hier mit Beispiel erklärt Es verständlicher :) dotnet-tricks.com/Tutorial/csharp/…
Prageeth Godage

2
@ JeppeStigNielsens Kommentar ist technisch gesehen die (einzige) richtige Antwort auf die eigentliche Frage des OP. Um ein Objekt an eine Methode zu übergeben, damit die Methode das Objekt ändern kann , übergeben Sie einfach den (Verweis auf das) Objekt als Wert an die Methode. Durch Ändern des Objekts innerhalb der Methode über das Objektargument wird das ursprüngliche Objekt geändert , obwohl die Methode eine eigene separate Variable enthält (die auf dasselbe Objekt verweist).
David R Tribble

Antworten:


1160

refteilt dem Compiler mit, dass das Objekt vor dem Aufrufen der Funktion initialisiert wird, während outdem Compiler mitgeteilt wird, dass das Objekt innerhalb der Funktion initialisiert wird.

Also, während refes in beide Richtungen geht, outist es nur out.


270
Eine weitere coole Sache für out ist, dass die Funktion dem out-Parameter zugewiesen werden muss. Es ist nicht erlaubt, es nicht zugewiesen zu lassen.
Daniel Earwicker

7
Gilt 'ref' nur für den Werttyp? Da der Referenztyp immer an ref übergeben wird.
fehlerhaft

3
Ja. Werttypen einschließlich Strukturen
Rune Grimstad

17
@faulty: Nein, ref gilt nicht nur für Werttypen. ref / out sind wie Zeiger in C / C ++, sie behandeln den Speicherort des Objekts (indirekt in C #) anstelle des direkten Objekts.
thr

52
@faulty: Gegenintuitiv werden Referenztypen in C # immer als Wert übergeben, es sei denn, Sie verwenden den Referenzspezifizierer. Wenn Sie myval = somenewval setzen, liegt der Effekt nur in diesem Funktionsumfang. Mit dem Schlüsselwort ref können Sie myval so ändern, dass es auf somenewval verweist.
JasonTrue

535

Der refModifikator bedeutet:

  1. Der Wert ist bereits eingestellt und
  2. Die Methode kann es lesen und ändern.

Der outModifikator bedeutet:

  1. Der Wert ist nicht festgelegt und kann von der Methode erst gelesen werden, wenn er festgelegt wurde.
  2. Die Methode muss es vor der Rückkehr festlegen.

30
Diese Antwort erklärt am klarsten und prägnantesten die Einschränkungen, die der Compiler bei der Verwendung des Schlüsselworts out im Gegensatz zum Schlüsselwort ref auferlegt.
Dr. Wily's Apprentice

5
Von MSDN: Ein ref-Parameter muss vor der Verwendung initialisiert werden, während ein out-Parameter vor der Übergabe nicht explizit initialisiert werden muss und alle vorherigen Werte ignoriert werden.
Shiva Kumar

1
Mit outkann es überhaupt innerhalb der Methode gelesen werden, bevor es von dieser Methode festgelegt wurde, wenn es vor dem Aufruf der Methode initialisiert wurde? Ich meine, kann die aufgerufene Methode lesen, was die aufrufende Methode als Argument an sie übergeben hat?
Panzercrisis

3
Panzercrisis, für "out" kann die aufgerufene Methode lesen, wenn sie bereits gesetzt ist. aber es muss es wieder einstellen.
Robert Jebakumar2

146

Nehmen wir an, Dom taucht in Peters Kabine über das Memo zu den TPS-Berichten auf.

Wenn Dom ein Ref-Argument wäre, hätte er eine gedruckte Kopie des Memos.

Wenn Dom ein Argument wäre, würde er Peter dazu bringen, eine neue Kopie des Memos auszudrucken, die er mitnehmen kann.


54
ref Dom hätte den Bericht mit Bleistift geschrieben, damit Peter ihn ändern könnte
Deebster

6
@Deebster du weißt, diese Metapher hat dir nie etwas angetan, warum musst du sie so quälen? ;)
Michael Blackburn

21
Unterhaltsam und doch lehrreich, Stackoverflow braucht mehr Beiträge wie diesen
Frank Visaggio

2
Nur für den Fall, dass jemand diese Antwort nur halb lustig findet, schauen Sie sich bitte den Film "Office Space" an.
DisplayName

und Dom und Peters Chef würden hinter Dom stehen (als Argument) und beide zwingen, daran zu arbeiten, es erneut auszudrucken, bis Peter Domd den Ausdruck übergibt
Patrick Artner

57

Ich werde mich an einer Erklärung versuchen:

Ich denke, wir verstehen, wie die Werttypen richtig funktionieren? Werttypen sind (int, long, struct usw.). Wenn Sie sie ohne einen ref-Befehl an eine Funktion senden, werden die Daten kopiert . Alles, was Sie mit diesen Daten in der Funktion tun, wirkt sich nur auf die Kopie aus, nicht auf das Original. Der Befehl ref sendet die IST-Daten und alle Änderungen wirken sich auf die Daten außerhalb der Funktion aus.

Ok, weiter zum verwirrenden Teil, Referenztypen:

Erstellen wir einen Referenztyp:

List<string> someobject = new List<string>()

Wenn Sie ein Objekt neu erstellen , werden zwei Teile erstellt:

  1. Der Speicherblock, der Daten für ein Objekt enthält .
  2. Ein Verweis (Zeiger) auf diesen Datenblock.

Wenn Sie nun ein Objekt in eine Methode ohne Referenz senden, kopiert es den Referenzzeiger , NICHT die Daten. Sie haben jetzt Folgendes:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject

Zwei Referenzen, die auf dasselbe Objekt verweisen. Wenn Sie eine Eigenschaft für ein Objekt mithilfe von Referenz2 ändern, wirkt sich dies auf dieselben Daten aus, auf die Referenz1 verweist.

 (inside method)  reference2.Add("SomeString");
 (outside method) reference1[0] == "SomeString"   //this is true

Wenn Sie Referenz2 auf Null setzen oder auf neue Daten verweisen, wirkt sich dies weder auf Referenz1 noch auf die Datenreferenz1 aus.

(inside method) reference2 = new List<string>();
(outside method) reference1 != null; reference1[0] == "SomeString" //this is true

The references are now pointing like this:
reference2 => new List<string>()
reference1 => someobject

Was passiert nun, wenn Sie ein Objekt per Referenz an eine Methode senden ? Der tatsächliche Verweis auf ein Objekt wird an die Methode gesendet. Sie haben jetzt nur noch einen Verweis auf die Daten:

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;

Aber was bedeutet das? Es verhält sich genauso wie das Senden eines Objekts, nicht mit Ausnahme von zwei Hauptsachen:

1) Wenn Sie die Referenz innerhalb der Methode auf Null setzen, wird die außerhalb der Methode auf Null gesetzt.

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true

2) Sie können jetzt die Referenz auf einen völlig anderen Datenort verweisen, und die Referenz außerhalb der Funktion zeigt jetzt auf den neuen Datenort.

 (inside method)  reference1 = new List<string>();
 (outside method) reference1.Count == 0; //this is true

Sie meinen, schließlich gibt es (im Referenzfall) nur einen Verweis auf Daten, aber zwei Alias ​​dafür. Recht?
Sadiq

3
Für die klare Erklärung positiv bewertet. Aber ich denke, dies beantwortet die Frage nicht, da es den Unterschied zwischen refund outParametern nicht erklärt .
Joyce Babu

1
Tolle. Kannst du das gleiche wie für das outSchlüsselwort erklären ?
Asif Mushtaq

28

ref ist rein und raus .

Sie sollten outbevorzugt verwenden, wo immer es für Ihre Anforderungen ausreicht.


nicht ganz, da die akzeptierte Antwort sich darauf bezieht, wenn direktional und nutzlos Werttypen ignoriert werden, wenn sie nicht zurückgegeben werden.
Kenny

@kenny: Kannst du bitte ein bisschen klarstellen - dh welche Wörter würdest du ändern, um den Geist der Antwort aufrechtzuerhalten, aber die Ungenauigkeit, die du wahrnimmst, beseitigen? Meine Antwort ist keine verrückte Vermutung eines Neulings, aber die Eile (Knappheit, Tippfehler) in Ihrem Kommentar scheint anzunehmen, dass dies der Fall ist. Ziel ist es, mit der geringsten Anzahl von Wörtern über den Unterschied nachzudenken.
Ruben Bartelink

(Übrigens bin ich mit Werttypen, Referenztypen, Referenzübergabe, Wertübergabe, COM und C ++ vertraut, falls Sie es nützlich finden sollten, in Ihrer Klarstellung auf diese Konzepte zu verweisen)
Ruben Bartelink

1
Objektreferenzen werden als Wert übergeben (außer bei Verwendung des Schlüsselworts "ref" oder "out"). Stellen Sie sich Objekte als ID-Nummern vor. Wenn eine Klassenvariable "Objekt # 1943" enthält und diese Variable als Wert an eine Routine übergeben wird, kann diese Routine Änderungen an Objekt # 1943 vornehmen, die Variable kann jedoch nicht auf etwas anderes als "Objekt # 1943" verweisen. Wenn die Variable als Referenz übergeben wurde, könnte die Routine den Variablenpunkt auf "Objekt # 5441" halten.
Supercat

1
@supercat: Ich mag deine Erklärung von ref vs val (und diese nachfolgende Anaologie). Ich denke, Kenny braucht nichts davon, was ihm erklärt wurde, (relativ) verwirrend wie seine Kommentare waren. Ich wünschte, wir könnten alle diese verdammten Kommentare einfach entfernen, da sie nur alle verwirren. Die Hauptursache für all diesen Unsinn scheint zu sein, dass Kenny meine Antwort falsch verstanden hat und noch kein einziges Wort hervorheben muss, das hinzugefügt / entfernt / ersetzt werden sollte. Keiner von uns dreien hat etwas aus der Diskussion gelernt, die wir noch nicht kannten, und die andere Antwort hat eine lächerliche Anzahl von positiven Stimmen.
Ruben Bartelink

18

aus:

In C # kann eine Methode nur einen Wert zurückgeben. Wenn Sie mehr als einen Wert zurückgeben möchten, können Sie das Schlüsselwort out verwenden. Der Modifikator out wird als Referenz zurückgegeben. Die einfachste Antwort ist, dass das Schlüsselwort "out" verwendet wird, um den Wert aus der Methode abzurufen.

  1. Sie müssen den Wert in der aufrufenden Funktion nicht initialisieren.
  2. Sie müssen den Wert in der aufgerufenen Funktion zuweisen, sonst meldet der Compiler einen Fehler.

ref:

Wenn Sie in C # einen Werttyp wie int, float, double usw. als Argument an den Methodenparameter übergeben, wird dieser als Wert übergeben. Wenn Sie den Parameterwert ändern, wirkt sich dies daher nicht auf das Argument im Methodenaufruf aus. Wenn Sie den Parameter jedoch mit dem Schlüsselwort "ref" markieren, wird er in der tatsächlichen Variablen wiedergegeben.

  1. Sie müssen die Variable initialisieren, bevor Sie die Funktion aufrufen.
  2. Es ist nicht zwingend erforderlich, dem Parameter ref in der Methode einen Wert zuzuweisen. Wenn Sie den Wert nicht ändern, müssen Sie ihn dann als "ref" markieren.

"In C # kann eine Methode nur einen Wert zurückgeben. Wenn Sie mehr als einen Wert zurückgeben möchten, können Sie das Schlüsselwort out verwenden." Wir können auch "ref" verwenden, um einen Wert zurückzugeben. Wir können also sowohl ref als auch out verwenden, wenn wir mehrere Werte von einer Methode zurückgeben möchten.
Ned

1
In c # 7 können Sie mit ValueTuples mehrere Werte zurückgeben.
Iman Bahrampour

13

Beispiel Hund erweitern, Katze. Die zweite Methode mit ref ändert das Objekt, auf das der Aufrufer verweist. Daher "Katze" !!!

    public static void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog". 
        Bar(ref myObject);
        Console.WriteLine(myObject.Name); // Writes "Cat". 
    }

    public static void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

    public static void Bar(ref MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

8

Da Sie einen Referenztyp (eine Klasse) übergeben, ist keine Verwendung erforderlich, refda standardmäßig nur eine Referenz auf das tatsächliche Objekt übergeben wird und Sie daher immer das Objekt hinter der Referenz ändern.

Beispiel:

public void Foo()
{
    MyClass myObject = new MyClass();
    myObject.Name = "Dog";
    Bar(myObject);
    Console.WriteLine(myObject.Name); // Writes "Cat".
}

public void Bar(MyClass someObject)
{
    someObject.Name = "Cat";
}

Solange Sie eine Klasse bestehen, müssen Sie diese nicht verwenden, refwenn Sie das Objekt in Ihrer Methode ändern möchten.


5
Dies funktioniert nur, wenn kein neues Objekt erstellt und zurückgegeben wird. Wenn ein neues Objekt erstellt wird, geht der Verweis auf das alte Objekt verloren.
Etsuba

8
Dies ist falsch - versuchen Sie Folgendes: add someObject = nullto Barend execute. Ihr Code wird Bareinwandfrei ausgeführt, da nur der Verweis auf die Instanz auf Null gesetzt wurde. Wechseln Sie jetzt Barzu Bar(ref MyClass someObject)und führen Sie es erneut aus. Sie erhalten eine, NullReferenceExceptionweil Fooder Verweis auf die Instanz ebenfalls auf Null gesetzt wurde.
Keith

8

refund outverhalten sich ähnlich, mit Ausnahme der folgenden Unterschiede.

  • refDie Variable muss vor der Verwendung initialisiert werden. outVariable kann ohne Zuordnung verwendet werden
  • outDer Parameter muss von der Funktion, die ihn verwendet, als nicht zugewiesener Wert behandelt werden. Wir können also initialisierte outParameter im aufrufenden Code verwenden, aber der Wert geht verloren, wenn die Funktion ausgeführt wird.


6

"Bäcker"

Das liegt daran, dass der erste Ihre Zeichenfolgenreferenz so ändert, dass er auf "Baker" zeigt. Das Ändern der Referenz ist möglich, weil Sie sie über das Schlüsselwort ref übergeben haben (=> eine Referenz auf eine Referenz auf eine Zeichenfolge). Der zweite Aufruf erhält eine Kopie des Verweises auf die Zeichenfolge.

Die Saite sieht auf den ersten Blick etwas Besonderes aus. Aber String ist nur eine Referenzklasse und wenn Sie definieren

string s = "Able";

dann ist s ein Verweis auf eine String-Klasse, die den Text "Able" enthält! Eine weitere Zuordnung zu derselben Variablen über

s = "Baker";

ändert nicht die ursprüngliche Zeichenfolge, sondern erstellt nur eine neue Instanz und zeigt auf diese Instanz!

Sie können es mit dem folgenden kleinen Codebeispiel versuchen:

string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);

Was erwartest du? Was Sie erhalten, ist immer noch "Able", da Sie nur die Referenz in s auf eine andere Instanz setzen, während s2 auf die ursprüngliche Instanz zeigt.

BEARBEITEN: Zeichenfolge ist auch unveränderlich, was bedeutet, dass es einfach keine Methode oder Eigenschaft gibt, die eine vorhandene Zeichenfolgeninstanz ändert (Sie können versuchen, eine in den Dokumenten zu finden, aber Sie werden keine finden :-)). Alle String-Manipulationsmethoden geben eine neue String-Instanz zurück! (Deshalb erzielen Sie häufig eine bessere Leistung, wenn Sie die StringBuilder-Klasse verwenden.)


1
Genau. Es ist also nicht unbedingt richtig zu sagen: "Da Sie einen Referenztyp (eine Klasse) übergeben, müssen Sie ref nicht verwenden."
Paul Mitchell

Theoretisch ist es richtig, dies zu sagen, weil er geschrieben hat, "damit es modifiziert werden kann", was bei Strings nicht möglich ist. Aber wegen unveränderlicher Objekte sind "ref" und "out" auch für Referenztypen sehr nützlich! (.Net enthält viele unveränderliche Klassen!)
mmmmmmmm

Ja, du hast Recht. Ich habe nicht an unveränderliche Objekte wie Strings gedacht, da die meisten Objekte veränderlich sind.
Albic

1
Nun, dies ist natürlich eine rätselhafte Antwort, die man in LQP sehen kann. Daran ist nichts auszusetzen, außer dass es eine lange und gründliche Antwort auf einen anderen Kommentar zu sein scheint (da in der ursprünglichen Frage Able und Baker in keiner ihrer Überarbeitungen erwähnt werden), als wäre dies ein Forum. Ich denke, das war noch nicht wirklich geklärt.
Nathan Tuggy

6

ref bedeutet, dass der Wert im Parameter ref bereits festgelegt ist und von der Methode gelesen und geändert werden kann. Die Verwendung des Schlüsselworts ref entspricht der Aussage, dass der Aufrufer für die Initialisierung des Werts des Parameters verantwortlich ist.


out teilt dem Compiler mit, dass die Initialisierung des Objekts in der Verantwortung der Funktion liegt, die die Funktion dem Parameter out zuweisen muss. Es ist nicht erlaubt, es nicht zugewiesen zu lassen.


5

Out: Eine return-Anweisung kann verwendet werden, um nur einen Wert von einer Funktion zurückzugeben. Mithilfe von Ausgabeparametern können Sie jedoch zwei Werte von einer Funktion zurückgeben. Ausgabeparameter sind wie Referenzparameter, außer dass sie Daten eher aus der Methode als in diese übertragen.

Das folgende Beispiel veranschaulicht dies:

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;

         Console.WriteLine("Before method call, value of a : {0}", a);

         /* calling a function to get the value */
         n.getValue(out a);

         Console.WriteLine("After method call, value of a : {0}", a);
         Console.ReadLine();

      }
   }
}

ref: Ein Referenzparameter ist eine Referenz auf einen Speicherort einer Variablen. Wenn Sie Parameter als Referenz übergeben, wird im Gegensatz zu Wertparametern kein neuer Speicherort für diese Parameter erstellt. Die Referenzparameter stellen denselben Speicherort dar wie die tatsächlichen Parameter, die der Methode zur Verfügung gestellt werden.

In C # deklarieren Sie die Referenzparameter mit dem Schlüsselwort ref. Das folgende Beispiel zeigt dies:

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* save the value of x */
         x = y;   /* put y into x */
         y = temp; /* put temp into y */
       }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;
         int b = 200;

         Console.WriteLine("Before swap, value of a : {0}", a);
         Console.WriteLine("Before swap, value of b : {0}", b);

         /* calling a function to swap the values */
         n.swap(ref a, ref b);

         Console.WriteLine("After swap, value of a : {0}", a);
         Console.WriteLine("After swap, value of b : {0}", b);

         Console.ReadLine();

      }
   }
}

4

ref und out funktionieren genauso wie das Übergeben von Referenzen und das Übergeben von Zeigern wie in C ++.

Für ref muss das Argument deklariert und initialisiert werden.

Für out muss das Argument deklariert werden, kann aber initialisiert werden oder nicht

        double nbr = 6; // if not initialized we get error
        double dd = doit.square(ref nbr);

        double Half_nbr ; // fine as passed by out, but inside the calling  method you initialize it
        doit.math_routines(nbr, out Half_nbr);

1
Sie können eine Variable inline deklarieren : out double Half_nbr.
Sebastian Hofmann

4

Authoring Time:

(1) Wir erstellen die aufrufende Methode Main()

(2) Es erstellt ein Listenobjekt (das ein Referenztypobjekt ist) und speichert es in der Variablen myList.

public sealed class Program 
{
    public static Main() 
    {
        List<int> myList = new List<int>();

Während der Laufzeit:

(3) Runtime weist einen Speicher auf dem Stapel bei # 00 zu, der breit genug ist, um eine Adresse zu speichern (# 00 = myList, da Variablennamen eigentlich nur Aliase für Speicherorte sind).

(4) Runtime erstellt ein Listenobjekt auf dem Heap am Speicherort #FF (alle diese Adressen sind zum Beispiel Sake).

(5) Runtime würde dann die Startadresse #FF des Objekts bei # 00 speichern (oder in Worten die Referenz des List-Objekts im Zeiger speichern myList).

Zurück zur Authoring-Zeit:

(6) Wir übergeben dann das List-Objekt als Argument myParamListan die aufgerufene Methode modifyMyListund weisen ihr ein neues List-Objekt zu

List<int> myList = new List<int>();

List<int> newList = ModifyMyList(myList)

public List<int> ModifyMyList(List<int> myParamList){
     myParamList = new List<int>();
     return myParamList;
}

Während der Laufzeit:

(7) Runtime startet die Aufrufroutine für die aufgerufene Methode und überprüft als Teil davon die Art der Parameter.

(8) Beim Auffinden des Referenztyps wird bei # 04 ein Speicher auf dem Stapel zum Aliasing der Parametervariablen zugewiesen myParamList.

(9) Es speichert dann auch den Wert #FF darin.

(10) Runtime erstellt ein Listenobjekt auf dem Heap am Speicherort # 004 und ersetzt #FF in # 04 durch diesen Wert (oder dereferenziert das ursprüngliche List-Objekt und verweist bei dieser Methode auf das neue List-Objekt).

Die Adresse in # 00 wird nicht geändert und behält den Verweis auf #FF bei (oder der ursprüngliche myListZeiger wird nicht gestört).


Das Schlüsselwort ref ist eine Compiler-Direktive zum Überspringen der Generierung von Laufzeitcode für (8) und (9). Dies bedeutet, dass für Methodenparameter keine Heap-Zuordnung erfolgt. Es wird der ursprüngliche Zeiger # 00 verwendet, um das Objekt bei #FF zu bearbeiten. Wenn der ursprüngliche Zeiger nicht initialisiert wird, hört die Laufzeit auf, sich zu beschweren, dass er nicht fortgesetzt werden kann, da die Variable nicht initialisiert wird

Das Schlüsselwort out ist eine Compiler-Direktive, die mit einer geringfügigen Änderung bei (9) und (10) ziemlich genau der Referenz entspricht. Der Compiler erwartet, dass das Argument nicht initialisiert ist, und fährt mit (8), (4) und (5) fort, um ein Objekt auf dem Heap zu erstellen und seine Startadresse in der Argumentvariablen zu speichern. Es wird kein nicht initialisierter Fehler ausgegeben und alle zuvor gespeicherten Referenzen gehen verloren.


3

Neben Sie neu zuweisen jemand anderes Variable auf eine andere Instanz einer Klasse ermöglicht, mehrere Werte usw. zurückkehren, mit refoder outläßt wissen , jemand anderes , was man von ihnen braucht und was Sie mit den Variablen zu tun beabsichtigen , sie bieten

  • Sie brauchen ref oder outmüssen nur die Dinge in der MyClassInstanz ändern , die im Argument übergeben wird someClass.

    • Der Aufruf der Methode werden Veränderungen wie , someClass.Message = "Hello World"ob Sie verwenden ref, outoder nichts
    • Durch das Schreiben im someClass = new MyClass()Inneren myFunction(someClass)wird das Objekt ausgetauscht, das nur someClassim Bereich der myFunctionMethode angezeigt wird. Die aufrufende Methode kennt immer noch die ursprüngliche MyClassInstanz, die sie erstellt und an Ihre Methode übergeben hat
  • Sie benötigen ref oder möchten das Out gegen ein ganz neues Objekt outaustauschen someClassund möchten, dass die aufrufende Methode Ihre Änderung sieht

    • Das Schreiben im someClass = new MyClass()Inneren myFunction(out someClass)ändert das Objekt, das von der aufgerufenen Methode gesehen wirdmyFunction

Andere Programmierer existieren

Und sie möchten wissen, was Sie mit ihren Daten tun werden. Stellen Sie sich vor, Sie schreiben eine Bibliothek, die von Millionen von Entwicklern verwendet wird. Sie möchten, dass sie wissen, was Sie mit ihren Variablen tun, wenn sie Ihre Methoden aufrufen

  • Mit using wird refdie Anweisung "Übergeben Sie eine Variable, die einem bestimmten Wert zugewiesen ist, wenn Sie meine Methode aufrufen. Beachten Sie, dass ich sie im Verlauf meiner Methode möglicherweise vollständig gegen etwas anderes austauschen kann. Erwarten Sie nicht, dass Ihre Variable auf das alte Objekt zeigt wenn ich fertig bin"

  • Mit using wird outdie Anweisung "Übergeben Sie eine Platzhaltervariable an meine Methode. Es spielt keine Rolle, ob sie einen Wert hat oder nicht; der Compiler zwingt mich, ihn einem neuen Wert zuzuweisen. Ich garantiere absolut, dass das Objekt, auf das Sie zeigen Variable, bevor Sie meine Methode aufgerufen haben, wird zu dem Zeitpunkt, an dem ich fertig bin , anders sein

Übrigens gibt es in C # 7.2 auch einen inModifikator

Dadurch wird verhindert, dass die Methode die übergebene Instanz gegen eine andere Instanz austauscht. Stellen Sie sich vor, Sie sagen zu diesen Millionen von Entwicklern: "Geben Sie mir Ihre ursprüngliche Variablenreferenz, und ich verspreche, Ihre sorgfältig ausgearbeiteten Daten nicht gegen etwas anderes auszutauschen." inhat einige Besonderheiten, und in einigen Fällen, z. B. wenn eine implizite Konvertierung erforderlich sein könnte, um Ihren Kurzfilm mit einem kompatibel zu machen, wird in intder Compiler vorübergehend einen Int erstellen, Ihren Kurzfilm darauf erweitern, ihn als Referenz übergeben und fertig stellen. Es kann dies tun, weil Sie erklärt haben, dass Sie sich nicht damit anlegen werden.


Microsoft hat dies mit den .TryParseMethoden für die numerischen Typen getan :

int i = 98234957;
bool success = int.TryParse("123", out i);

Indem Sie den Parameter markieren, während outer hier aktiv erklärt: "Wir werden Ihren sorgfältig ausgearbeiteten Wert von 98234957 definitiv gegen etwas anderes austauschen."

Natürlich müssen sie das für Dinge wie das Parsen von Werttypen, denn wenn die Analysemethode den Wertetyp nicht gegen etwas anderes austauschen könnte, würde es nicht sehr gut funktionieren. Aber stellen Sie sich vor, es gäbe eine fiktive Methode in einigen Bibliothek, die Sie erstellen:

public void PoorlyNamedMethod(out SomeClass x)

Sie können sehen, dass es eine ist out, und Sie können somit wissen, dass, wenn Sie Stunden damit verbringen, Zahlen zu knacken, die perfekte SomeClass erstellt wird:

SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);

Nun, das war Zeitverschwendung, all diese Stunden zu brauchen, um diesen perfekten Unterricht zu machen. Es wird definitiv weggeworfen und durch PoorlyNamedMethod ersetzt


3

Für diejenigen, die eine prägnante Antwort suchen.

Sowohl refals auch outSchlüsselwörter werden zum Übergeben verwendet reference.


Eine Variable des refSchlüsselworts muss einen Wert haben oder auf ein Objekt verweisen oder null bevor es übergeben wird.


Im Gegensatz dazu refmuss eine Variable des outSchlüsselworts einen Wert haben oder auf ein Objekt oder null nach dessen Übergabe verweisen sowie vor dem Übergeben keinen Wert haben oder auf ein Objekt verweisen .


2

Um die vielen hervorragenden Erklärungen zu veranschaulichen, habe ich die folgende Konsolen-App entwickelt:

using System;
using System.Collections.Generic;

namespace CSharpDemos
{
  class Program
  {
    static void Main(string[] args)
    {
      List<string> StringList = new List<string> { "Hello" };
      List<string> StringListRef = new List<string> { "Hallo" };

      AppendWorld(StringList);
      Console.WriteLine(StringList[0] + StringList[1]);

      HalloWelt(ref StringListRef);
      Console.WriteLine(StringListRef[0] + StringListRef[1]);

      CiaoMondo(out List<string> StringListOut);
      Console.WriteLine(StringListOut[0] + StringListOut[1]);
    }

    static void AppendWorld(List<string> LiStri)
    {
      LiStri.Add(" World!");
      LiStri = new List<string> { "¡Hola", " Mundo!" };
      Console.WriteLine(LiStri[0] + LiStri[1]);
    }

    static void HalloWelt(ref List<string> LiStriRef)
     { LiStriRef = new List<string> { LiStriRef[0], " Welt!" }; }

    static void CiaoMondo(out List<string> LiStriOut)
     { LiStriOut = new List<string> { "Ciao", " Mondo!" }; }
   }
}
/*Output:
¡Hola Mundo!
Hello World!
Hallo Welt!
Ciao Mondo!
*/
  • AppendWorld: Eine Kopie von StringListnamed LiStriwird übergeben. Zu Beginn der Methode verweist diese Kopie auf die ursprüngliche Liste und kann daher zum Ändern dieser Liste verwendet werden. LiStriVerweist später auf ein anderes List<string>Objekt innerhalb der Methode, das die ursprüngliche Liste nicht beeinflusst.

  • HalloWelt: LiStriRefist ein Alias ​​des bereits initialisierten ListStringRef. Das übergebene List<string>Objekt wird verwendet, um ein neues zu initialisieren, daher refwar dies erforderlich.

  • CiaoMondo: LiStriOutist ein Alias ​​von ListStringOutund muss initialisiert werden.

Wenn eine Methode nur das Objekt ändert, auf das von der übergebenen Variablen verwiesen wird, lässt der Compiler outSie nicht zu und Sie sollten es nicht verwenden, refda dies nicht den Compiler, sondern den Leser des Codes verwirren würde. Wenn die Methode das übergebene Argument auf ein anderes Objekt verweisen lässt, verwenden Sie es reffür ein bereits initialisiertes Objekt und outfür Methoden, die ein neues Objekt für das übergebene Argument initialisieren müssen. Außerdem refund outbenimm dich gleich.


1

Sie sind ziemlich gleich - der einzige Unterschied besteht darin, dass eine Variable, die Sie als out-Parameter übergeben, nicht initialisiert werden muss und die Methode, die den ref-Parameter verwendet, sie auf etwas setzen muss.

int x;    Foo(out x); // OK 
int y;    Foo(ref y); // Error

Ref-Parameter beziehen sich auf Daten, die möglicherweise geändert werden. Out-Parameter beziehen sich auf Daten, die eine zusätzliche Ausgabe für die Funktion darstellen (z. B. int.TryParse), die bereits den Rückgabewert für etwas verwenden.


1

Unten habe ich ein Beispiel gezeigt, das sowohl Ref als auch out verwendet . Jetzt werden Sie alle über ref und out geklärt.

Im unten genannten Beispiel, wenn ich kommentiere // myRefObj = new myClass {Name = "ref außerhalb genannt !!"}; In der Zeile wird die Fehlermeldung "Verwendung der nicht zugewiesenen lokalen Variablen 'myRefObj'" angezeigt , es tritt jedoch kein solcher Fehler auf .

Verwendungszweck Ref : Wenn wir eine Prozedur mit einem in-Parameter aufrufen und derselbe Parameter zum Speichern der Ausgabe dieses Prozesses verwendet wird.

Verwendungszweck von Out: Wenn wir eine Prozedur ohne Parameter in aufrufen und derselbe Parameter verwendet wird, um den Wert von diesem Prozess zurückzugeben. Beachten Sie auch die Ausgabe

public partial class refAndOutUse : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        myClass myRefObj;
        myRefObj = new myClass { Name = "ref outside called!!  <br/>" };
        myRefFunction(ref myRefObj);
        Response.Write(myRefObj.Name); //ref inside function

        myClass myOutObj;
        myOutFunction(out myOutObj);
        Response.Write(myOutObj.Name); //out inside function
    }

    void myRefFunction(ref myClass refObj)
    {
        refObj.Name = "ref inside function <br/>";
        Response.Write(refObj.Name); //ref inside function
    }
    void myOutFunction(out myClass outObj)
    {
        outObj = new myClass { Name = "out inside function <br/>" }; 
        Response.Write(outObj.Name); //out inside function
    }
}

public class myClass
{
    public string Name { get; set; }
} 

1
 public static void Main(string[] args)
    {
        //int a=10;
        //change(ref a);
        //Console.WriteLine(a);
        // Console.Read();

        int b;
        change2(out b);
        Console.WriteLine(b);
        Console.Read();
    }
    // static void change(ref int a)
    //{
    //    a = 20;
    //}

     static void change2(out int b)
     {
         b = 20;
     }

Sie können diesen Code überprüfen, der Ihnen den vollständigen Unterschied beschreibt, wenn Sie "ref" verwenden. Dies bedeutet, dass Sie diesen int / string bereits initialisiert haben

Aber wenn Sie "out" verwenden, funktioniert es unter beiden Bedingungen, ob Sie diesen int / string initialisieren oder nicht, aber Sie müssen diesen int / string in dieser Funktion initialisieren


1

Ref: Das Schlüsselwort ref wird verwendet, um ein Argument als Referenz zu übergeben. Dies bedeutet, dass der Wert dieses Parameters in der Methode in der aufrufenden Methode wiedergegeben wird. Ein Argument, das mit einem ref-Schlüsselwort übergeben wird, muss in der aufrufenden Methode initialisiert werden, bevor es an die aufgerufene Methode übergeben wird.

Out: Das Schlüsselwort out wird auch verwendet, um ein Argument wie das Schlüsselwort ref zu übergeben. Das Argument kann jedoch übergeben werden, ohne ihm einen Wert zuzuweisen. Ein Argument, das mit einem out-Schlüsselwort übergeben wird, muss in der aufgerufenen Methode initialisiert werden, bevor es zur aufrufenden Methode zurückkehrt.

public class Example
{
 public static void Main() 
 {
 int val1 = 0; //must be initialized 
 int val2; //optional

 Example1(ref val1);
 Console.WriteLine(val1); 

 Example2(out val2);
 Console.WriteLine(val2); 
 }

 static void Example1(ref int value) 
 {
 value = 1;
 }
 static void Example2(out int value) 
 {
 value = 2; 
 }
}

/* Output     1     2     

Ref und out in Methodenüberladung

Sowohl ref als auch out können nicht gleichzeitig bei der Methodenüberladung verwendet werden. Ref und Out werden zur Laufzeit unterschiedlich behandelt, aber zur Kompilierungszeit werden sie gleich behandelt (CLR unterscheidet nicht zwischen beiden, während IL für ref und out erstellt wurde).


0

Vom Standpunkt einer Methode, die einen Parameter empfängt, besteht der Unterschied zwischen refund outdarin, dass C # erfordert, dass Methoden outvor der Rückgabe in jeden Parameter schreiben müssen und mit einem solchen Parameter nichts anderes tun dürfen, als ihn als outParameter zu übergeben oder an ihn zu schreiben , bis es entweder als outParameter an eine andere Methode übergeben oder direkt geschrieben wurde. Beachten Sie, dass einige andere Sprachen solche Anforderungen nicht stellen. Eine virtuelle oder Schnittstellenmethode, die in C # mit einem outParameter deklariert ist, kann in einer anderen Sprache überschrieben werden, die solchen Parametern keine besonderen Einschränkungen auferlegt.

Vom Standpunkt des Aufrufers aus geht C # unter vielen Umständen davon aus, dass beim Aufrufen einer Methode mit einem outParameter die übergebene Variable geschrieben wird, ohne zuvor gelesen worden zu sein. Diese Annahme ist möglicherweise nicht korrekt, wenn Methoden aufgerufen werden, die in anderen Sprachen geschrieben sind. Zum Beispiel:

struct MyStruct
{
   ...
   myStruct(IDictionary<int, MyStruct> d)
   {
     d.TryGetValue(23, out this);
   }
}

Wenn myDictionaryeine IDictionary<TKey,TValue>Implementierung identifiziert wird , die in einer anderen Sprache als C # geschrieben ist, obwohl sie MyStruct s = new MyStruct(myDictionary);wie eine Zuweisung aussieht, kann sie möglicherweise sunverändert bleiben.

Beachten Sie, dass in VB.NET geschriebene Konstruktoren im Gegensatz zu denen in C # keine Annahmen darüber treffen, ob aufgerufene Methoden outParameter ändern und alle Felder bedingungslos löschen. Das oben erwähnte seltsame Verhalten tritt nicht bei Code auf, der vollständig in VB oder vollständig in C # geschrieben ist, sondern kann auftreten, wenn in C # geschriebener Code eine in VB.NET geschriebene Methode aufruft.


0

Wenn Sie Ihren Parameter als Referenz übergeben möchten, sollten Sie ihn initialisieren, bevor Sie den Parameter an die Funktion übergeben. Andernfalls zeigt der Compiler selbst den Fehler an. Bei out-Parametern müssen Sie den Objektparameter jedoch nicht initialisieren, bevor Sie ihn an den übergeben Methode. Sie können das Objekt in der aufrufenden Methode selbst initialisieren.


-3

Beachten Sie, dass der Referenzparameter, der innerhalb der Funktion übergeben wird, direkt bearbeitet wird.

Zum Beispiel,

    public class MyClass
    {
        public string Name { get; set; }
    }

    public void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog".
    }

    public void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

Dies wird Hund schreiben, nicht Katze. Daher sollten Sie direkt an someObject arbeiten.


6
Während hier alles ziemlich wahr ist, erklärt es nicht wirklich den Unterschied zwischen nach Wert durch Bezugnahme oder nach außen. Bestenfalls erklärt es zur Hälfte den Unterschied zwischen Referenz- und Wert- / unveränderlichen Typen.
Conrad Frix

Wenn Sie möchten, dass dieser Code cat schreibt, übergeben Sie dieses Objekt bitte zusammen mit dem 'ref'-Schlüssel wie folgt: public static void Bar (ref MyClass someObject), Bar (ref myObject);
Daniel Botero Correa

-4

Ich bin vielleicht nicht so gut darin, aber Zeichenfolgen (obwohl sie technisch Referenztypen sind und auf dem Heap leben) werden als Wert übergeben, nicht als Referenz?

        string a = "Hello";

        string b = "goodbye";

        b = a; //attempt to make b point to a, won't work.

        a = "testing";

        Console.WriteLine(b); //this will produce "hello", NOT "testing"!!!!

Aus diesem Grund benötigen Sie ref, wenn Änderungen außerhalb des Funktionsumfangs vorgenommen werden sollen. Andernfalls übergeben Sie keine Referenz.

Soweit mir bekannt ist, benötigen Sie nur ref für Strukturen / Werttypen und Zeichenfolge selbst, da Zeichenfolge ein Referenztyp ist, der vorgibt, dies zu sein, aber kein Werttyp ist.

Ich könnte hier allerdings völlig falsch liegen, ich bin neu.


5
Willkommen bei Stack Overflow, Edwin. Soweit ich weiß, werden Zeichenfolgen wie jedes andere Objekt als Referenz übergeben. Sie können verwirrt sein, weil Zeichenfolgen unveränderliche Objekte sind. Daher ist es nicht so offensichtlich, dass sie als Referenz übergeben werden. Stellen Sie sich vor, diese Zeichenfolge hätte eine Methode namens Capitalize(), die den Inhalt der Zeichenfolge in Großbuchstaben ändert. Wenn Sie dann Ihre Zeile a = "testing";durch ersetzen a.Capitalize();würden, wäre Ihre Ausgabe "HALLO", nicht "Hallo". Einer der Vorteile unveränderlicher Typen besteht darin, dass Sie Referenzen weitergeben können und sich keine Sorgen machen müssen, dass anderer Code den Wert ändert.
Don Kirkby

2
Es gibt drei grundlegende Arten von Semantiken, die ein Typ verfügbar machen kann: veränderbare Referenzsemantik, veränderbare Wertesemantik und unveränderliche Semantik. Betrachten Sie die Variablen x und y eines Typs T mit dem Feld oder der Eigenschaft m und nehmen Sie an, dass x nach y kopiert wird. Wenn T eine Referenzsemantik hat, werden Änderungen an xm von ym beobachtet. Wenn T eine Wertesemantik hat, kann man xm ändern, ohne ym zu beeinflussen. Wenn T eine unveränderliche Semantik hat, werden sich weder xm noch ym jemals ändern. Unveränderliche Semantik kann entweder durch Referenz- oder Wertobjekte simuliert werden. Strings sind unveränderliche Referenzobjekte.
Supercat
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.