Laden...

[Artikel] Parameter-Übergabemechanismen: call by value vs. call by reference (ref/out)

Erstellt von Vertexwahn vor 18 Jahren Letzter Beitrag vor 14 Jahren 101.127 Views
V
Vertexwahn Themenstarter:in
179 Beiträge seit 2005
vor 18 Jahren
[Artikel] Parameter-Übergabemechanismen: call by value vs. call by reference (ref/out)

C# und Übergabemechanismen: call by value vs. call by reference

Motivation

Wissen Sie wie call by value und call by reference in C# funktioniert? Was…? Sie sind ein alter C++ Programmierer und in C# ist doch alles genau so? Hier ein Motivationsbeispiel:

using System;

class Auto
{
	public Auto(int AnzahlReifen)
	{
		m_AnzahlReifen = AnzahlReifen;
	}
	public int m_AnzahlReifen;
}

public class MyApp
{
	public static void Main() 
	{
		Auto MeinAuto = new Auto(4);
		foo(MeinAuto);
		System.Console.WriteLine(MeinAuto.m_AnzahlReifen);  // gibt 4 aus
	}
    
	static void foo(Auto irgendeinAuto)
	{
		irgendeinAuto = new Auto(6);
	}
}

Wenn sie jetzt verwundert vor dem Bildschirm hocken und sich fragen warum obiges Programm die Zahl 4 ausgibt und nicht die Zahl 6, dann sind Sie ein Leser meine Zielgruppe. Falls nicht, werden sie im folgendem auch nichts neues mehr erfahren.

Alles call by value

Um es kurz zu machen: Parameterübergabe in C# erfolgt implizit immer in Form von call by value.

Jetzt fragen Sie sich sicherlich warum folgendes geht und warum das nicht call by reference ist:

using System;

class Auto
{
	public Auto(int AnzahlReifen)
	{
		m_AnzahlReifen = AnzahlReifen;
	}
	public int m_AnzahlReifen;
}

public class MyMain 
{
    
	public static void Main() 
	{
		Auto MeinAuto = new Auto(4);
		foo(MeinAuto);
		System.Console.WriteLine(MeinAuto.m_AnzahlReifen);  // gibt 6 aus
	}
    
	static void foo(Auto irgendeinAuto)
	{
		irgendeinAuto.m_AnzahlReifen = 6;
	}
}

Die Erklärung ist ganz einfach. Übergibt man eine Referenz an eine Methode, so wird ihr Inhalt kopiert. Was ist der Inhalt eine Referenz? Der Inhalt einer Referenz ist nur ein Verweis, der angibt, wo sich das zugehörige Objekt befindet. Wenn man also eine Referenz an eine Methode übergibt wird eine Kopie der Referenz an die Funktion übergeben.

Auto A = new Auto();
doSomething(A);

void doSomething(Auto B) 
{
   // mache etwas mit B
}

Bei dem Aufruf der Methode doSomething wird eine zweite Referenz angelegt, die auf das Objekt Auto verweist. Verändert die Referenz B das Objekt so wirkt sich dies auf das Auto Objekt aus.

Man kann in der Methode doSomething das Auto Objekt verändern, aber nicht die Referenz A.

Sehen Sie sich jetzt noch einmal das Motivationsbeispiel an und stellen Sie fest warum 4 und nicht 6 ausgegeben wird.

Man kann in der Methode doSomething nicht die Referenz A auf ein anderes Objekt zeigen lassen, da man nur mit einer Kopie arbeitet.

Auto A = new Auto();
doSomething(A);

void doSomething(Auto B) 
{
	B = new Aute(); // beeinflusst nicht die Referenz A
}

Es bleibt festzuhalten: C# arbeitet mit call-by-value, auch wenn man Referenzen übergibt. Bei Java ist es genau so.

Call by refernece

In C# ist auch call by reference bei der Paramterübergabe möglich. Dabei wird vor jedem Parameter der per call by reference übergeben werden soll das Schlüsselwort ref gestellt:

using System;

class Auto
{
	public Auto(int AnzahlReifen)
	{
		m_AnzahlReifen = AnzahlReifen;
	}
	public int m_AnzahlReifen;
}

public class MyMain 
{
    
	public static void Main() 
	{
		Auto MeinAuto = new Auto(4);
		foo(ref MeinAuto);
		System.Console.WriteLine(MeinAuto.m_AnzahlReifen);  // gibt 6 aus
	}
    
	static void foo(ref Auto irgendeinAuto)
	{
		irgendeinAuto = new Auto(6);
	}
}

Das Schlüsselwort out

Häufig besitzen Methoden Ergebnisparameter. Ergebnisparameter sind Parameter, die nur auf der linken Seite einer Anweisung auftreten. Ihr Wert vor der Ausführung der Methode ist also bedeutungslos. Beispiel:

using System;

class MyClass
{
	static void Main(string[] args)
	{
		int ergebnis = 0;
		add(3, 2, ref ergebnis);
		System.Console.WriteLine(ergebnis); // gibt 5 aus
	}

	static void add(int x, int y, ref int ergebnisparameter)
	{
		ergebnisparameter = x + y;
	}
}

Der Wert von ergebnis ist bedeutungslos, da dieser sowieso mit dem Wert 5 (x+y) überschrieben wird. Die Initialisierung der Variablen ergebnis mit einem bestimmten Wert (z. B. 0) ist unsinnig, da das Ergebnis von x und y abhängig ist. Lässt man die Initialisierung weg, bekommt man eine Fehlermeldung der Art „Verwendung einer nicht zugewiesener lokalen Variablen“:

using System;

class MyClass
{
	static void Main(string[] args)
	{
		int ergebnis; // keine Initialisierung
		add(3, 2, ref ergebnis); // FEHLER:
          // Verwendung einer nicht zugewiesener lokalen Variablen
		System.Console.WriteLine(ergebnis);
	}

	static void add(int x, int y, ref int ergebnisparameter)
	{
		ergebnisparameter = x + y;
	}
}

Abhilfe schafft hier das Schlüsselwert out, welches einen Parameter als Ergebnisparameter kennzeichnet. Parameter, die mit out gekennzeichnet sind, müssen nicht initialisiert werden:

using System;

class MyClass
{
	static void Main(string[] args)
	{
		int ergebnis;
		add(3, 2, out ergebnis);
		System.Console.WriteLine(ergebnis); // gibt 5 aus

		System.Console.ReadLine();
	}

	static void add(int x, int y, out int ergebnisparameter)
	{
		ergebnisparameter = x + y;
	}
}

Grundsätzlich ist es guter Stil Variablen gleich zu initialisieren, damit man davor bewahrt wird eine nicht initialisierte Variable zu verwenden. Ein Compiler, der eine Warnung bei benutzen einer nicht initialisierten Variablen ausspuckt, ist ein wahrer Segen. Mit bedacht kann man aber das Schlüsselwort out ruhigen Gewissens einsetzen.

Ein weiterer Vorteil des out Parameters ist die Rückgabe von mehreren Ergebnissen. Natürlich kann man dies auch mit Rückgabewerten von Methoden erreichen, jedoch ist man hier auf einen einzelnen Rückgabewert beschränkt. Eine Methode kann nicht zwei Werte zurückgeben. In solchen Fällen kann man zwar überlegen, ob es nicht sinnvoll ist beide Rückgabeparameter zu einen neuen Typ zusammenfassen und dadurch einen einzelnen Rückgabewert zu schaffen, jedoch gibt es Situationen, in denen eine Zusammenfassung aufgrund der Semantik der Daten einfach nicht sinnvoll ist, da die Daten logisch nicht zusammengehören oder es ist einfach bequemer zwei „Rückgabewerte“ bereitzustellen.

Beispiel:


private void getProxySettings(out string IP, out int Port)
{
    // IP und Port als String zurueckgeben [hier fehlt Code!]
} 

Schlusswort

Ich hoffe der Unterschied zwischen call by value und call by reference ist klar geworden. Der direkte Zugriff auf als public Elemente, wie in den obigen Beispielen gezeigt (MeinAuto.m_AnzahlReifen) ist natürlich kein sauberer Stil. Die Art des Zugriffs auf public Elemente, wurde gewählt um den Umfang der Beispielprogramme möglichst gering und übersichtlich zu halten. Anstelle von direktem Zugriff auf Datenelemente sollte man so genannte Getter- und Settermethoden benutzen. In C# sind diese als Properties bekannt. Wer diese nicht schon lange kennt, sollte danach mal googeln.

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo Vertexwahn,

ich finde deinen Artikel gut. Nette Einleitung, gute Motivation.

Du solltest allerdings 'out' noch in dem Artikel erklären, ist ja schnell gemacht. Zumal man, wenn man bei google nur nach 'out' sucht, nicht wirklich fündig wird.

Was mir auch nicht gefällt ist das public Field m_AnzahlReifen. Gerade wenn Anfänger den Artikel lesen, werden die dann auf eine falsche Fährte geführt. Ich weiß aber leider auch keine richtig Lösung, weil die an sich sinnvollen Properties hier das Verständnis vermutlich erschweren. Du könntest aber zumindest in einem Nachwort klarstellen, dass man public Fields meiden sollte.

herbivore

V
Vertexwahn Themenstarter:in
179 Beiträge seit 2005
vor 18 Jahren

Du solltest allerdings 'out' noch in dem Artikel erklären, ist ja schnell gemacht. Zumal man, wenn man bei google nur nach 'out' sucht, nicht wirklich fündig wird.

Was mir auch nicht gefällt ist das public Field m_AnzahlReifen

Habe die Änderungen vorgenommen!

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo Vertexwahn,

fein! Wird immer besser. Vielleicht könntest du noch eine Antwort auf die Frage "Warum soll ich out benutzen, wenn ich ebenso gut einen Rückgabewert nehmen kann?" geben, die man sich bei dem konkreten Beispiel leicht stellen kann.

Dann musst du eigentlich nur noch den Titel ändern und einen Admin dazu bringen, den Beitrag ins Forum Artikel zu verschieben.

herbivore

V
Vertexwahn Themenstarter:in
179 Beiträge seit 2005
vor 18 Jahren

Warum soll ich out benutzen, wenn ich ebenso gut einen Rückgabewert nehmen kann?

weil es nicht 2 Rückgabewerte gibt 😉 mmh... soll ich das wirklich noch dazu schreiben

V
Vertexwahn Themenstarter:in
179 Beiträge seit 2005
vor 18 Jahren

Beispiel


private void getSupporter(out string IP, out int Port)
{
	// IP und Port als String zurueckgeben
}

mmh..

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo Vertexwahn,

soll ich das wirklich noch dazu schreiben

ja 🙂

Das Beispiel ist doch auch gut.

herbivore

V
Vertexwahn Themenstarter:in
179 Beiträge seit 2005
vor 18 Jahren

ok ich habs eingebaut 😉

4.221 Beiträge seit 2005
vor 18 Jahren

Was für einen FAQ-Artikel noch fehlt ist der folgende Hinweis:

Obwohl in C# ein ref-Parameter inizialisiert werden muss damit er übergeben werden kann sollte man in DLL's (welche in verschiedenen Sprachen verwendet werden können) trotzdem den Wert überprüfen bevor er verwendet wird... dies aus folgenden Gründen:

a) ein Wert kann mit null inizialisiert werden (geht in allen Sprachen)

b) Andere .Net Sprachen unterscheiden nicht zwischen ref und out.... so ist es z.B: in VB.Net problemlos möglich mit ByRef einen nicht inizialisierten Wert an einen ref-Parameter zu übergeben (die Prüfung in VB entfällt, da VB keine Unterscheidung zwischen ref und out kennt).

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo zusammen,

der Artikel steht jetzt in 'Artikel'. 🙂

herbivore

B
119 Beiträge seit 2005
vor 18 Jahren

Wissen Sie wie call by value und call by reference in C# funktioniert? Was…? Sie sind ein alter C++ Programmierer und in C# ist doch alles genau so?

Nur so als kleine Anmerkung, nachdem ich auch von C++ gewechselt bin: Es funktioniert in der Tat genau so. Auch in C++ wird alles, sofern kein & dabei steht, per Value übergeben, also bei "Wertetypen" eben der Inhalt selbst und bei Zeigern eben die Adresse. Das Äquivalent zum ref Schlüsselwort in C# ist dann eben das & nach dem Typ des Parameters.


// Speicherlecks werden in diesem Beispiel einfach mal ignoriert ..

void foo1(Auto* auto) {
    auto = new Auto(6);
    // letztendlich wird 4 ausgegeben
}

void foo2(Auto* auto) {
    auto->setAnzahlReifen(6);
    // letztendlich wird 6 ausgegeben
}

void foo3(Auto*& auto) {
    auto = new Auto(6);
    // letztendlich wird 6 ausgegeben
}

grüße

V
Vertexwahn Themenstarter:in
179 Beiträge seit 2005
vor 18 Jahren

Es funktioniert in der Tat genau so

Du hast Recht.

Hier der C++ Code zum Testen im Compiler:


#include <iostream>
using namespace std;

class Auto
{
public:
    Auto(int AnzahlReifen)
    {
        m_AnzahlReifen = AnzahlReifen;
    }

	int m_AnzahlReifen;
};

// Speicherlecks werden in diesem Beispiel einfach mal ignoriert ..

void foo1(Auto* a) {
    a = new Auto(6);
    // letztendlich wird 4 ausgegeben
}

void foo2(Auto* b) {
	b->m_AnzahlReifen = 6;
    // letztendlich wird 6 ausgegeben
}

void foo3(Auto*& c) {
    c = new Auto(6);
    // letztendlich wird 6 ausgegeben
}

int main()
{
	Auto x(4);
	
	foo1(&x);
	cout<<x.m_AnzahlReifen<<endl;

	foo2(&x);
	cout<<x.m_AnzahlReifen<<endl;

	x.m_AnzahlReifen = 4;

	Auto *ptr = &x;
	foo3(ptr);
	cout<<x.m_AnzahlReifen<<endl;

	cout<<ptr->m_AnzahlReifen<<endl; // gibt 6 aus.

	cin.get();
}

100 Beiträge seit 2006
vor 17 Jahren

Danke für den guten Artikel.
Für einen Umsteiger von C++ wie mich war er sehr hilfreich.

5.941 Beiträge seit 2005
vor 17 Jahren

Hallo zusammen

Dank diesem Artikel und da es bei mir noch nicht 100% "Klick" gemacht hat, habe ich mich ein wenig in dieses Thema vertieft.
Nach diversen Tests und Vergleichen habe ich es schlussendlich verstanden.
Auf Idee meiner Schwester, habe ich einen Artikel dazu geschrieben, um es noch einmal zu verinnerlichen und eventuell anderen damit zu helfen.

Da ich denke dass mein Artikel eine gute Ergänzung zu diesem Artikel darstellt, poste ich den Link hier nach der Rücksprache und Zustimmung von Herbivore.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011

M
15 Beiträge seit 2007
vor 17 Jahren

Hi,

Da ich jetzt schon öfter bei euch auf der Seite gelandet bin melde ich mich nun einfach mal an 😉

Die Beschreibung von out und der Art der Argumentübergabe ist gut geworden allerdings habe ich eine kleine kritik 🙂
(Gerade was die Strategie der 2 Rückgabewerte betrifft...)

Ihr solltet definitiv erwähnen das es beim out-Schlüsselwort einzig und allein um einen geschwindigkeitsvorteil geht.
Out exsistiert in C# als eine art "Pointerersatz" (ja ich weiß es ist nicht das selbe) und ist ein **Sprachmittel der C-familie das nicht den regeln der OOP entspricht **(Kapselung wird gebrochen) sondern eher historisch (mit hinein) gewachsen ist.

Es ist kein guter Programmierstil out zu nutzen wenn es nicht von nöten ist.

Es gibt 3 grundlegende möglichkeinen out in C# zu vermeiden.

  1. Methoden im Object schreiben und nicht C-style Methoden die Objecte bekommen.( das ist C da hat es einen Grund gehabt so zu schreiben - es gab keine Objekte) gerade dieser Punkt kann dem "out" wort jeden Geschwindigkeitsvorteil nehmen.
  2. Methoden die ein Object bekommen und das selbe verändert wieder zurückgeben (1 ist zu preferieren)
  3. Mehrere Rückgabewerte ? Array nutzen oder Strukturen oder Obekte ... und Daten verpacken (Strukturen preferiert das ist ihr Sinn - das Verpacken von Argumenten um diese weiterzugeben 🙂)

Man sieht das all diese Methoden vollkommen OO sind und sich damit besser strukturierter (und modernerer )Code schreiben lässt.

Es mag viele C/C++ Programmierer geben die jetzt aufschreien wollen gerade c++ Programmierer haben dazu aber keinen Grund da es auch in C++ schon ein ** falscher weg ist übermäsig mit Methoden die Objekte bekommen und nicht Objekte die Methoden haben zu arbeiten** (kommt daher das c++ Programmierer mal aus C kamen und das eingeschleppt haben 😄)

(IN C haben Pointer natürlich einen Sinn gerade was Hardwarenähe betrifft und in C++ auch ... ich will auch nicht sagen das OUT in C# quatsch ist ABER wenn es geht sollte man es umgehen.)

Ansonsten viele Grüße - eine tolle Seite 🙂

Rechtschreiben 5 -_- sorry editedit x-D

49.485 Beiträge seit 2005
vor 17 Jahren

Hallo Moooitic,

im Prinzip hast du recht, nur ist das kein spezifisches Problem von out, sondern ist bei der Verwendung von return ganz genauso zu beachten.

Ihr solltet definitiv erwähnen das es beim out-Schlüsselwort einzig und allein um einen geschwindigkeitsvorteil geht.

Nö, das würde ich nicht sagen. Die TryParse-Methoden sind ein gutes Beispiel für die sinnvolle und oo-konforme Verwendung von out.

herbivore

B
1.529 Beiträge seit 2006
vor 17 Jahren

Die TryParse-Methoden sind ein gutes Beispiel für die sinnvolle und oo-konforme Verwendung von out.

Nun ja, soweit würde ich nicht gehen. TryParse hat ja auch bloß das Problem zweier Rückgabewerte.
Prinzipiell sind out-Parameter wirklich (oo-gesehen) überflüssig. Oo wäre es, wenn Parse ein entsprechendes Objekt zurückgibt. Allerdings macht das die Schreibweise oftmals komplizierter.

// statt
int myInt;
if (int.TryParse( myString, myInt))
{
   // ...
}

// müsste man
int.ParseResult ipr = int.Parse( myString );
if (ipr.Successful)
{
   int myInt = ipr.Value;
   // ...
}
// schreiben
C
489 Beiträge seit 2007
vor 16 Jahren

Hallo

Original von Vertexwahn

Auto A = new Auto();  
doSomething(A);  
  
void doSomething(Auto B)  
{  
B = new Aute(); // beeinflusst nicht die Referenz A  
}  

Das soll sicher

b= new Auto(); 

heißen. Soll nicht nerven, sondern nur mein erster Beitrag zur Community sein.

Sonst toller Artikel, der mir als c++er sehr geholfen hat. Gibt es eigentlich eine
Entsprechung für const in c#?

chrische

L
43 Beiträge seit 2007
vor 16 Jahren

Doch const 😉 So wiet ich weiß geht das bei prozeduren aber nicht

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo chrische5,

Konstante Parameter

Prophylaktisch: Bitte keine langen Diskussionen in Artikel-Threads.

herbivore

M
194 Beiträge seit 2008
vor 15 Jahren

Sehr guter Artikel. Der bringt endlich mal Licht ins Dunkel der ByValue und ByReference Geschichte. Da denkt man, man hat schon lange alles verstanden und auf einmal war es doch ganz anders. Vielen Dank für die Mühe.

"Indem Sie über dieses ernste Thema lachen disqualifizieren Sie sich selbst."
mrleeh.de

115 Beiträge seit 2008
vor 14 Jahren

Leider sind die Dateien im Startposting nicht mehr verfügbar. Gibt es eine neue URL?

Grüße,
der Michael

5.742 Beiträge seit 2007
vor 14 Jahren

Leider sind die Dateien im Startposting nicht mehr verfügbar. Gibt es eine neue URL?

Der Inhalt war - soweit ich weiß - identisch mit dem Text im ersten Posting.

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo Lumbra, hallo winSharp93,

Der Inhalt war - soweit ich weiß - identisch mit dem Text im ersten Posting.

so ist es. Da der Inhalt also weiterhin vollständig enthalten ist, nur eben nicht mehr in unterschiedlichen Dateiformaten vorliegt, habe ich die jetzt unnütz gewordenen Links entfernt.

herbivore