Übergeben von Objekten als Referenz oder Wert in C #

233

In C # habe ich immer gedacht, dass nicht-primitive Variablen als Referenz und primitive Werte als Wert übergeben wurden.

Wenn Sie also ein nicht primitives Objekt an eine Methode übergeben, wirkt sich alles, was mit dem Objekt in der Methode getan wird, auf das übergebene Objekt aus. (C # 101 Zeug)

Ich habe jedoch festgestellt, dass dies beim Übergeben eines System.Drawing.Image-Objekts nicht der Fall zu sein scheint. Wenn ich ein system.drawing.image-Objekt an eine andere Methode übergebe und ein Bild auf dieses Objekt lade, dann lasse diese Methode den Gültigkeitsbereich verlassen und gehe zur aufrufenden Methode zurück. Wird dieses Bild nicht auf das ursprüngliche Objekt geladen?

Warum ist das?

Michael
quelle
20
Alle Variablen werden standardmäßig in C # als Wert übergeben. Bei Referenztypen übergeben Sie den Wert der Referenz .
Andrew Barber

Antworten:

502

Objekte werden überhaupt nicht übergeben. Standardmäßig wird das Argument ausgewertet und sein Wert als Wert als Anfangswert des Parameters der von Ihnen aufgerufenen Methode übergeben. Der wichtige Punkt ist nun, dass der Wert eine Referenz für Referenztypen ist - eine Möglichkeit, zu einem Objekt (oder Null) zu gelangen. Änderungen an diesem Objekt sind für den Aufrufer sichtbar. Das Ändern des Werts des Parameters, um auf ein anderes Objekt zu verweisen, ist jedoch nicht sichtbar, wenn Sie den Wert "Übergeben" verwenden. Dies ist die Standardeinstellung für alle Typen.

Wenn Sie Pass-by-Referenz verwenden möchten, Sie müssen verwenden outoder ref, ob der Parametertyp ein Werttyp ist oder ein Referenztyp. In diesem Fall wird die Variable selbst effektiv als Referenz übergeben, sodass der Parameter denselben Speicherort wie das Argument verwendet - und Änderungen am Parameter selbst vom Aufrufer gesehen werden.

So:

public void Foo(Image image)
{
    // This change won't be seen by the caller: it's changing the value
    // of the parameter.
    image = Image.FromStream(...);
}

public void Foo(ref Image image)
{
    // This change *will* be seen by the caller: it's changing the value
    // of the parameter, but we're using pass by reference
    image = Image.FromStream(...);
}

public void Foo(Image image)
{
    // This change *will* be seen by the caller: it's changing the data
    // within the object that the parameter value refers to.
    image.RotateFlip(...);
}

Ich habe einen Artikel, der hier viel ausführlicher behandelt wird . Grundsätzlich bedeutet "Referenzübergabe" nicht, was Sie denken, dass es bedeutet.

Jon Skeet
quelle
2
Dein Recht, das habe ich nicht gesehen! Ich lade image = Image.FromFile (..) und das ersetzte das variable Bild und änderte das Objekt nicht! :) Na sicher.
Michael
1
@Adeem: Nicht ganz - es gibt kein "Parameterobjekt", es gibt das Objekt, auf das sich der Wert des Parameters bezieht. Ich denke, Sie haben die richtige Idee, aber Terminologie ist wichtig :)
Jon Skeet
2
Wenn wir Schlüsselwörter refund outvon c # entfernen, ist es in Ordnung zu sagen, dass c # Parameter auf die gleiche Weise wie Java übergibt, dh immer nach Wert. Gibt es einen Unterschied zu Java?
Breitband
1
@broadband: Ja, der Standardübergabemodus ist by-value. Obwohl C # natürlich Zeiger und benutzerdefinierte Werttypen hat, ist alles etwas komplizierter als in Java.
Jon Skeet
3
@ Vippy: Nein, überhaupt nicht. Es ist eine Kopie der Referenz . Ich schlage vor, Sie lesen den verlinkten Artikel.
Jon Skeet
18

Ein weiteres Codebeispiel, um dies zu demonstrieren:

void Main()
{


    int k = 0;
    TestPlain(k);
    Console.WriteLine("TestPlain:" + k);

    TestRef(ref k);
    Console.WriteLine("TestRef:" + k);

    string t = "test";

    TestObjPlain(t);
    Console.WriteLine("TestObjPlain:" +t);

    TestObjRef(ref t);
    Console.WriteLine("TestObjRef:" + t);
}

public static void TestPlain(int i)
{
    i = 5;
}

public static void TestRef(ref int i)
{
    i = 5;
}

public static void TestObjPlain(string s)
{
    s = "TestObjPlain";
}

public static void TestObjRef(ref string s)
{
    s = "TestObjRef";
}

Und die Ausgabe:

TestPlain: 0

TestRef: 5

TestObjPlain: Test

TestObjRef: TestObjRef

vmg
quelle
2
Grundsätzlich MUSS der Referenztyp immer noch als Referenz übergeben werden, wenn wir die Änderungen in der Anruferfunktion sehen möchten.
Unbreakable
1
Zeichenfolgen sind unveränderliche Referenztypen. Unveränderlich bedeutet, dass es nach seiner Erstellung nicht mehr geändert werden kann. Bei jeder Änderung an einer Zeichenfolge wird eine neue Zeichenfolge erstellt. Aus diesem Grund mussten Zeichenfolgen als 'ref' übergeben werden, um Änderungen in der aufrufenden Methode zu erhalten. Andere Objekte (z. B. Mitarbeiter) können ohne 'ref' übergeben werden, um die Änderungen in der aufrufenden Methode zurückzugewinnen.
Himalaya Garg
1
@vmg, laut HimalayaGarg ist dies kein sehr gutes Beispiel. Sie müssen ein anderes Referenztypbeispiel einfügen, das nicht unveränderlich ist.
Daniel
11

Viele gute Antworten wurden hinzugefügt. Ich möchte noch einen Beitrag leisten, vielleicht wird es etwas mehr klarstellen.

Wenn Sie eine Instanz als Argument an die Methode übergeben, wird copydie Instanz übergeben. Wenn es sich bei der übergebenen Instanz um eine value type(in der stack) befindliche Instanz handelt, übergeben Sie die Kopie dieses Werts. Wenn Sie sie also ändern, wird sie nicht im Aufrufer angezeigt. Wenn es sich bei der Instanz um einen Referenztyp handelt, übergeben Sie die Kopie der Referenz (befindet sich wieder in der stack) an das Objekt. Sie haben also zwei Verweise auf dasselbe Objekt. Beide können das Objekt ändern. Wenn Sie jedoch innerhalb des Methodenkörpers ein neues Objekt instanziieren, verweist Ihre Kopie der Referenz nicht mehr auf das ursprüngliche Objekt, sondern auf das gerade erstellte neue Objekt. Sie haben also 2 Referenzen und 2 Objekte.

OlegI
quelle
Dies sollte die gewählte Antwort sein!
JAN
Ich stimme vollkommen zu! :)
JOSEFtw
8

Ich denke, es ist klarer, wenn du es so machst. Ich empfehle, LinqPad herunterzuladen, um solche Dinge zu testen.

void Main()
{
    var Person = new Person(){FirstName = "Egli", LastName = "Becerra"};

    //Will update egli
    WontUpdate(Person);
    Console.WriteLine("WontUpdate");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateImplicitly(Person);
    Console.WriteLine("UpdateImplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateExplicitly(ref Person);
    Console.WriteLine("UpdateExplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
}

//Class to test
public class Person{
    public string FirstName {get; set;}
    public string LastName {get; set;}

    public string printName(){
        return $"First name: {FirstName} Last name:{LastName}";
    }
}

public static void WontUpdate(Person p)
{
    //New instance does jack...
    var newP = new Person(){FirstName = p.FirstName, LastName = p.LastName};
    newP.FirstName = "Favio";
    newP.LastName = "Becerra";
}

public static void UpdateImplicitly(Person p)
{
    //Passing by reference implicitly
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

public static void UpdateExplicitly(ref Person p)
{
    //Again passing by reference explicitly (reduntant)
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

Und das sollte ausgegeben werden

WontUpdate

Vorname: Egli, Nachname: Becerra

UpdateImplicitly

Vorname: Favio, Nachname: Becerra

UpdateExplicitly

Vorname: Favio, Nachname: Becerra

Egli Becerra
quelle
und was ist mit öffentlicher statischer Leere WhatAbout (Person p) {p = neue Person () {Vorname = "Vorname", Nachname = "Nachname"}; }. :)
Marin Popov
4

Wenn Sie das System.Drawing.ImageTypobjekt an eine Methode übergeben, übergeben Sie tatsächlich eine Referenzkopie an dieses Objekt.

Wenn Sie also innerhalb dieser Methode ein neues Bild laden, laden Sie es mit einer neuen / kopierten Referenz. Sie nehmen keine Änderungen am Original vor.

YourMethod(System.Drawing.Image image)
{
    //now this image is a new reference
    //if you load a new image 
    image = new Image()..
    //you are not changing the original reference you are just changing the copy of original reference
}
Haris Hasan
quelle
-1

In Pass By Reference Sie fügen nur "ref" in die Funktionsparameter ein und eine weitere Sache, die Sie als "static" deklarieren sollten, da main static (# public void main(String[] args)) ist!

namespace preparation
{
  public  class Program
    {
      public static void swap(ref int lhs,ref int rhs)
      {
          int temp = lhs;
          lhs = rhs;
          rhs = temp;
      }
          static void Main(string[] args)
        {
            int a = 10;
            int b = 80;

  Console.WriteLine("a is before sort " + a);
            Console.WriteLine("b is before sort " + b);
            swap(ref a, ref b);
            Console.WriteLine("");
            Console.WriteLine("a is after sort " + a);
            Console.WriteLine("b is after sort " + b);  
        }
    }
}
user5593590
quelle