Laden...

Übergabe einer Referenz und Übergabe einer Kopie bei Methoden

Erstellt von Lalgeriano vor 8 Jahren Letzter Beitrag vor 8 Jahren 4.740 Views
L
Lalgeriano Themenstarter:in
21 Beiträge seit 2015
vor 8 Jahren
Übergabe einer Referenz und Übergabe einer Kopie bei Methoden

Hallo zusammen,

ich mal wieder 😁 Nach einer zeitlichen Pausen, die ich dann hauptsächlich zum Wiederholen meines bis lang erlernten Inhaltes durch das Buch "Schrödinger programmiert C#" habe ich mich vor gut 1.5 Woche an das nächste Thema im Buch, nämlich den Methoden gewagt.

Soweit, so gut. Allerdings habe ich ein, anscheinend, riesiges Problem beim Thema Übergabe einer Referenz und Übergabe einer Kopie. Ich weiß, dass per Default bei Datentypen wie int, double etc. pp. immer eine Kopie beim Methodenaufruf übergeben wird und so die originalen Variablen gleich bleiben. Dies kann ich aber explizit mit dem Schlüsselwort ref ändern, sodass der neue Wert, welcher sich durch die Methode gibt, in die Variablen geschrieben wird.

Ein Beispiel aus dem Buch, das ich für die Verständnis mal ein wenig "erweitert" habe ist folgendes.


static void Main(string[] args)
        {
            double var1 = 2.5;
            double var2 = 3.8;
            Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", var1, var2);
            SwapOne(ref var1, ref var2);
            Console.WriteLine("Der neue Wert ist {0} und {1}", var1, var2);
            Console.ReadLine();

            double var3 = 5.5;
            double var4 = 8.5;
            Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", var3, var4);
            SwapTwo(var3, var4);
            Console.WriteLine("Der neue Wert beträgt {0} und {1}", var3, var4);
            Console.ReadLine();
        }

        static void SwapOne (ref double a, ref double b)
        {
            double temp = a;
            a = b;
            b = temp;
        }

        static void SwapTwo (double x, double y)
        {
            double temp = x;
            x = y;
            y = temp;
        }

Dort sehe ich ja sehr gut, dass durch die 1. Methode mit Ref die Variablen auch vertauscht worden sind, was in diesem Beispiel ja auch durchaus Sinn der Sache ist. Im zweiten wurde nichts beschrieben und es ist so, als wäre gar nichts passiert.

Soweit habe ich die Verständnis, aber irgendwie ist der entscheidende Stein noch nicht ins Rollen gekommen, der mir das ganze logisch erklingen lässt. Ich habe im Galileo OpenBook gelesen, genau so wie hier in der Knowledgebase und auch generell im Internet, aber irgendwie fehlt mir da der Denkanstoss, der mich von der Stelle tritt, auf der ich momentan festklebe.

Eventuell hat jemand noch eine Definition bzw. eigene Erklärung für beide Varianten parat und kann gegebenenfalls erklären, welche Methode für welchen Zweck eher genutzt wird.

Wenn ich die Kopie übergebe und der neue Wert nicht in den Variablen gespeichert wird, wo ist da konkret der Sinn der Methode? Irgendwie scheint mir dort ein Puzzleteil zu fehlen.

Über Hilfe wäre ich dementsprechend sehr dankbar! Vielen Dank im Voraus und lieben Gruß 🙂

Lalgeriano

F
10.010 Beiträge seit 2004
vor 8 Jahren

Wenn ich die Kopie übergebe und der neue Wert nicht in den Variablen gespeichert wird, wo ist da konkret der Sinn der Methode? Irgendwie scheint mir dort ein Puzzleteil zu fehlen.

Du versuchst jetzt bereits zu viel zu verstehen, was in den folgenden Kapiteln ( hoffentlich ) noch erklärt wird.

Aber nur damit du ruhiger schläfst:
Warum muss denn die Methode etwas machen was die ursprünglichen Werte ändert?
Was wenn du damit irgendetwas anderes berechnest?
Oder etwas zeichnest, druckst, speicherst.....

4.939 Beiträge seit 2008
vor 8 Jahren

Bei der Swap-Methode ist es ja verständlich, daß man dort die übergebenen Parameter ändern will (denn die andere Methode ohne ref hat, wie du ja selbst schon erkannt hast, keine Auswirkung).
Bei den meisten Methoden aber - wie FZelle schon geschrieben hat - möchte man eben nur eine Kopie übergeben. Bei den ref-Parametern muß man ja explizit beim Aufruf immer zusätzlich 'ref variablenname' angeben. Für einfache Berechnungsmethoden kann man aber auch ebenso ganze Terme als Parameter übergeben, z.B.


int BerechneQuadrat(int x)
{
    return x * x;
}

// Aufruf
int y = berechneQuadrat(5 + 2*10);

ref macht also nur bei den Methoden Sinn, wo man innerhalb der Methode diesem Parameter einen neuen Wert zuweist. Besser ist es jedoch dafür immer den Rückgabewert der Methode zu benutzen (nur wenn man mehrere Rückgabewerte hat, so macht ref (sowie out) Überhaupt einen Sinn).

3.003 Beiträge seit 2006
vor 8 Jahren

ref macht also nur bei den Methoden Sinn, wo man innerhalb der Methode diesem Parameter einen neuen Wert zuweist. Besser ist es jedoch dafür immer den Rückgabewert der Methode zu benutzen (nur wenn man mehrere Rückgabewerte hat, so macht ref (sowie out) Überhaupt einen Sinn).

Ganz genau. Trotz des ref-Schlüsselwortes kann der Code, wenn durch eine Methode mehrere Variablen beeinflusst werden, auf die Weise aber unleserlich werden. Wenn möglich (und es keiner der seltenen Fälle ist, wo ref wirklich sinnvoll ist), sollte man eine Methode also ein definiertes Objekt entgegennehmen und zurückliefern lassen. Kennen und verstehen solltest du als Beginner vor allem den Unterschied zwischen Wert- und Referenztypen.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

463 Beiträge seit 2009
vor 8 Jahren

Ganz ehrlich? Kaufe dir ein vernünftiges Buch zum C# lernen und ich denke, du wirst viele Probleme weniger haben!

L
Lalgeriano Themenstarter:in
21 Beiträge seit 2015
vor 8 Jahren

Hallo Leute,

erst einmal Danke für eure Hilfe. Wenn ich es richtig verstehe, dann sollte man auf Methoden mit ref auf Grund eines guten Programmierstils quasi verzichten, sofern dies natürlich möglich ist. Der ref Typ scheint dann ja eher speziell sein zu müssen.

@LaTino: Mit definiertem Objekt meinst Du konkret? Eine vorher festgelegte Variable als Parameter übergeben?

@All: Eventuell versuche ich einfach alles zu schnell und auf Anhieb zu verstehen, das habe ich wohl so an mich 😄 Ich denke immer, wenn ich es jetzt nicht verstehe und weiter lese wird dies irgendwann nochmal wichtig, wo die Verständnis dann bereits Voraussetzung ist und ich so nur schwer folgen kann.

Ansonsten bin ich mit dem Buch eigentlich zufrieden, habe allerdings auch keine andere Alternativen zur Hand, außer das Galileo OpenBook (welches mir bzgl. ref aber auch nicht geholfen hat) und vorher das andere C# Buch von Galileo bzw. den Rheinwerken (das es jetzt in der neusten Auflage gibt). Ich habe aber generell auch nichts dagegen zwei Bücher über das selbe Thema parallel zu lesen, damit habe ich persönlich beim Lernen öfters gute Erfahrungen gesammelt. Also, falls jemand noch Anregungen zu Literatur hat, welche wirklich gut und verständlich ist, immer gerne her damit!

Lieben Gruß

3.003 Beiträge seit 2006
vor 8 Jahren

@LaTino: Mit definiertem Objekt meinst Du konkret? Eine vorher festgelegte Variable als Parameter übergeben?

Nein, ich meine ein in seiner Form fest vorgegebenes Objekt, das der Kommunikation dient und die jeweiligen Parameter für die Methoden kapselt. So etwas:


bool RefMethod(string ref param1, string ref param2)
{ /*.. */ }

//wird zu:
class MyDataObject 
{
    public string param1 { get; set; }
    public string param2 { get; set; }
}

bool NewMethod(MyDataObject data)
{ /*.. */ }

Man nutzt also, dass data sowieso by reference übergeben wird (wie jedes Referenzobjekt).

out- oder ref-Parameter haben durchaus ihre Existenzberechtigung - schau dir mal diverse TryParse()-Methoden z.B. von int oder decimal an.

Leider werden sie gern in Situationen benutzt, wo man für eine Methode gern mehrere Werttyp-Objekte aendern würde und sie dann eben verwendet, anstatt das Klassendesign zu überdenken.

LaTino
Edit: weil's grad passt und ich über diesen Code just in diesem Moment gestolpert bin: hier kann man recht gut sehen, was für furchtbare Konstrukte entstehen, wenn man lieber alles durchballert, statt die Objektkommunikation zu organisieren (ja, die out-Params werden tatsaechlich hier nicht benutzt).


/* anonymisierte Bezeichner */
private static void StaticMethod(DateTime someDate, SqlDecimal someData, string someString, uint someUint, SqlDecimal otherData, ComplexObject complexData)
{
    SqlDecimal outParam1;
    SqlDecimal outParam2;

    ISomeInterface classHandle = new ConcreteClass();
    classHandle.DoSomething(complexData, someUint, someDate, null, AnEnum.Enumvalue, someData, otherData, someString, out outParam1, out outParam2);
}

Das ist nicht schön, nicht gut wartbar, nicht gut lesbar, und fehleranfaellig obendrein.

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

A
764 Beiträge seit 2007
vor 8 Jahren

Hallo Lalgeriano

um mal bei deinem Beispiel zu bleiben:


        public class SwapObject
        {
            public double X { get; set; }
            public double Y { get; set; }
        }

        static void Main(string[] args)
        {
            SwapObject swapObject = new SwapObject();
            swapObject.X = 5.5;
            swapObject.Y = 8.5;
            Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", swapObject.X, swapObject.Y);
            SwapObject resultSwapObject = SwapTwo(swapObject);
            Console.WriteLine("Der neue Wert beträgt {0} und {1}", resultSwapObject.X, resultSwapObject.Y);
            Console.ReadLine();
        }

        static SwapObject SwapTwo(SwapObject swapObject)
        {
            SwapObject resultSwapObject = new SwapObject();
            resultSwapObject.Y = swapObject.X;
            resultSwapObject.X = swapObject.Y;

            return resultSwapObject;
        }

und mit Referenz:


        public class SwapObject
        {
            public double X { get; set; }
            public double Y { get; set; }
        }

        static void Main(string[] args)
        {
            SwapObject swapObject = new SwapObject();
            swapObject.X = 5.5;
            swapObject.Y = 8.5;
            Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", swapObject.X, swapObject.Y);
            SwapTwo(swapObject);
            Console.WriteLine("Der neue Wert beträgt {0} und {1}", swapObject.X, swapObject.Y);
            Console.ReadLine();
        }

        static void SwapTwo(SwapObject swapObject)
        {
            double tmp = swapObject.X;
            swapObject.X = swapObject.Y;
            swapObject.Y = tmp;
        }

Probier damit mal ein bisschen rum, vielleicht fällt dir sogar was auf 😛

Edit: ref-Keyword im 2. Beispiel entfernt. Siehe Diskussion

1.040 Beiträge seit 2007
vor 8 Jahren

Ich muss auf deine Beispiele doch nochmal eingehen, Alf Ator.
Für mich sind sie beide falsch, weil hier verschiedene Dinge vermischt werden.

Das übergebene, selbst definierte Objekt soll ja grade dafür da sein, um keinen Rückgabewert (Beispiel 1) und keinen ref-Parameter (Beispiel 2) zu haben.

Für Anfänger sind die Beispiele eher verwirrend als hilfreich.
((Oder sollten sie zeigen, wie man es nicht macht? 8))){gray}

L
Lalgeriano Themenstarter:in
21 Beiträge seit 2015
vor 8 Jahren

Nein, ich meine ein in seiner Form fest vorgegebenes Objekt, das der Kommunikation dient und die jeweiligen Parameter für die Methoden kapselt. So etwas:

Ah, alles klar. Verstanden, danke für die nähere Erklärung! 😃

@Alf Ator: Ich werde mal versuchen dies in aller Ruhe nachzuvollziehen ohne mich verwirren zu lassen und hoffe es klappt 😄

3.003 Beiträge seit 2006
vor 8 Jahren

Für mich sind sie beide falsch, weil hier verschiedene Dinge vermischt werden.

Hm, ja, nein, äh. Also, nicht direkt falsch.

Referenzübergabe von Referenztypen ist...kompliziert zu erklären, ohne Anfänger komplett zu verwirren (da hast du Recht). Von daher ist das Beispiel unglücklich. Auf der anderen Seite ist das Beispiel großartig in dem Moment, wo der Anfänger glaubt, das Prinzip verstanden zu haben.

Ich würde mich sehr schwer tun, zu erklären, was genau da passiert, ohne die Worte "stack", "heap", und Beispiele aus der C-Welt.

Folgender Satz aus der MSDN trifft's schon gut:

Verwechseln Sie nicht das Konzept der Übergabe durch einen Verweis mit dem Konzept der Verweistypen. Die beiden Konzepte sind nicht identisch. Ein Methodenparameter kann durch ref geändert werden, unabhängig davon, ob es sich um einen Werttyp oder ein Verweistyp handelt. Es gibt keine Boxing-Konvertierung eines Werttyps, wenn er durch einen Verweis übergeben wird.

Wenn ich mir den Satz dreimal durchlese, weiß ich, was sie meinen und wie das mit dem Beispiel zusammenhängt. Erklären - weniger. Mir würde auch spontan kein Beispiel aus meiner Erfahrung einfallen, wo es sinnvoll gewesen wäre, eine Referenz auf einen Referenztypen zu übergeben. Beim Umgang mit Referenztypen als Parameter verhält sich C# schon so, wie man intuitiv erwartet, und das ist sehr gut so.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

W
955 Beiträge seit 2010
vor 8 Jahren

Hm, ja, nein, äh. Also, nicht direkt falsch. Dann muß er im zweiten Beispiel das ref-Keyword im SwapTwo-Aufruf entfernen. Das Ding kompiliert wahrscheinlich nicht einmal.

3.003 Beiträge seit 2006
vor 8 Jahren

Dann muß er im zweiten Beispiel das ref-Keyword im SwapTwo-Aufruf entfernen. Das Ding kompiliert wahrscheinlich nicht einmal.

Hast Recht, allerdings im zweiten Beispiel im Aufruf das ref-Keyword reintun - wenn er das vorhat zu zeigen, was ich glaube.


Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", swapObject.X, swapObject.Y);
SwapTwo(ref swapObject);
Console.WriteLine("Der neue Wert beträgt {0} und {1}", swapObject.X, swapObject.Y);

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

W
955 Beiträge seit 2010
vor 8 Jahren

Nein muß er nicht. Es wird erst benötigt wenn er in SwapTwo ein neues Objekt erzeugen will und es wieder zurück übergeben will.

3.003 Beiträge seit 2006
vor 8 Jahren

Mea culpa, das Beispiel komplett falsch gelesen. Dachte, SwapTwo im zweiten Beispiel wäre dasselbe wie im ersten...okay, dann kapier ich die Intention der Beispiele auch nicht mehr^^

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

A
764 Beiträge seit 2007
vor 8 Jahren

Was ein kleines ref alles ausmachen kann 😛

Ich habe das ref im zweiten Beispiel nur als Verdeutlichung reingemacht, dass es sich um einen Referenztypen handelt. Wenn ich so drüber nachdenke, ist es tatsächlich verwirrend. Deswegen nehme ich es wieder raus.

Danke 😃

1.040 Beiträge seit 2007
vor 8 Jahren

Ich finde die Beispiele nach wie vor unglücklich. 😁
Im 1. Beispiel kann man nämlich theoretisch auch das übergebene Objekte verändern (so wie im 2. Beispiel), sodass dann sowohl das zurückgebene als auch das übergebene Objekt nach dem Funktionsaufruf nicht mehr dem übergebenen Objekt vor dem Funktionsaufruf entsprechen.

Aus meiner Sicht wären Werttypen für die Beispiele besser gewesen, schließlich sollte ja dann schon einmal das "ref" vorkommen (was eben bei Referenztypen wenig Sinn macht). 😉

        static void Main(string[] args)
        {
            double X = 5.5;
            double Y = 8.5;
            Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", X, Y);
            SwapTwo(X, Y);
            Console.WriteLine("Der neue Wert beträgt {0} und {1}", X, Y);
            Console.ReadLine();
        }

        static void SwapTwo(double x, double y)
        {
            double tmp = x;
            x = y;
            y = tmp;
        }

In diesem Beispiel hat der Aufruf der Funktion SwapTwo keinerlei Auswirkungen, die übergebenen Werte bleiben gleich.
Sinn machen solche Aufrufe z.B. wenn in der Methode etwas gespeichert, gedruckt etc. werden soll (wie von FZelle schon erwähnt) oder in Kombination mit einem Rückgabeparameter, z.B. eine Multiplikation von 2 Werten.

        static void Main(string[] args)
        {
            double X = 5.5;
            double Y = 8.5;
            Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", X, Y);
            SwapTwo(ref X, ref Y);
            Console.WriteLine("Der neue Wert beträgt {0} und {1}", X, Y);
            Console.ReadLine();
        }

        static void SwapTwo(ref double x, ref double y)
        {
            double tmp = x;
            x = y;
            y = tmp;
        }

In diesem Beispiel hat der Aufruf der Funktion SwapTwo Auswirkungen, die übergebenen Werte werden tatsächlich getauscht. "Schuld" daran ist das ref-Schlüsselwort.
Für den Fall, das 2 Werte getauscht werden sollen, kann man das durchaus so machen. Es kann aber unleserlich werden, wenn man es zu oft nutzt.

        public class SwapObject
        {
            public double X { get; set; }
            public double Y { get; set; }
        }

        static void Main(string[] args)
        {
            SwapObject swapObject = new SwapObject();
            swapObject.X = 5.5;
            swapObject.Y = 8.5;
            Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", swapObject.X, swapObject.Y);
            SwapTwo(swapObject);
            Console.WriteLine("Der neue Wert beträgt {0} und {1}", swapObject.X, swapObject.Y);
            Console.ReadLine();
        }

        static void SwapTwo(SwapObject swapObject)
        {
            double tmp = swapObject.X
            swapObject.X = swapObject.Y;
            swapObject.Y = tmp;
        }

In diesem Beispiel hat der Aufruf der Funktion SwapTwo Auswirkungen, da die Werte IM übergebenen Objekt getauscht werden. Dies ist die Variante, die LaTino vorgeschlagen hat.
Das 2. und 3. Beispiel liefern am Ende das gleiche Ergebnis, das 3. Beispiel ist durch das definierte Objekt aber leichter zu verstehen, wartbarer, etc. pp. 🙂

A
764 Beiträge seit 2007
vor 8 Jahren

das 3. Beispiel ist durch das definierte Objekt aber leichter zu verstehen, wartbarer, etc. pp. 🙂

Darauf wollte ich eigentlich hinaus. Sehr gut 👍

3.003 Beiträge seit 2006
vor 8 Jahren

Wenn ich darf, noch ein Beispiel 4, oder "wann ref auch bei Referenztypen Sinn ergibt" 😉.


        public class SwapObject
        {
            public double X { get; set; }
            public double Y { get; set; }
        }

        static void Main(string[] args)
        {
            SwapObject swapObject = new SwapObject();
            swapObject.X = 5.5;
            swapObject.Y = 8.5;
            Console.WriteLine("Der Wert beider Variablen beträgt {0} und {1}", swapObject.X, swapObject.Y);
            SwapByValue(swapObject);
            Console.WriteLine("Der neue Wert beträgt {0} und {1}", swapObject.X, swapObject.Y);
            SwapByReference(ref swapObject);
            Console.WriteLine("Der neue Wert beträgt {0} und {1}", swapObject.X, swapObject.Y);
            Console.ReadLine();
        }

        static void SwapByValue(SwapObject swapObject)
        {
            swapObject = new SwapObject {X = swapObject.Y, Y = swapObject.X};
        }

        static void SwapByReference(ref SwapObject swapObject)
        {
            swapObject = new SwapObject { X = swapObject.Y, Y = swapObject.X };
        }

Exakt derselbe Code in den Methoden.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

1.040 Beiträge seit 2007
vor 8 Jahren

Es lässt sich darüber streiten, ob das Sinn macht. 😉
Wenn es unbedingt ein neues Objekt sein soll, was aber in die gleiche Variable gespeichert werden soll, würde ich es so lösen:

static SwapObject SwapByReference(SwapObject swapObject)
{
    return new SwapObject { X = swapObject.Y, Y = swapObject.X };
}

//Aufruf
swapObject = SwapByReference(swapObject);
3.003 Beiträge seit 2006
vor 8 Jahren

Japp, würde ich auch. Zudem haut mir resharper sowieso in der SwapByValue()-Methode auf die Finger. Aber darum geht's nicht, sondern Sinn ist, zu zeigen, dass sich auch Referenzobjekte bei Übergabe als Referenz anders verhalten können. (Das mag jetzt für die meisten Leser keine Überraschung sein - hoffe ich 😉 )

Man übergibt eben nur einen Zeiger, und die Geschichte ist in C# mit seinem versteckten Zeigerkonzept einem Anfänger ziemlich schwierig zu erklären. Finde ich.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)