C# Language
Werttyp vs. Referenztyp
Suche…
Syntax
- Übergeben als Referenz: public void Double (ref int numberToDouble) {}
Bemerkungen
Einführung
Werttypen
Werttypen sind die einfachere der beiden. Werttypen werden häufig verwendet, um Daten selbst darzustellen. Eine Ganzzahl, ein Boolean oder ein Punkt im 3D-Raum sind Beispiele für gute Werttypen.
Werttypen (Strukturen) werden mit dem Schlüsselwort struct deklariert. Ein Beispiel zur Deklaration einer neuen Struktur finden Sie im Abschnitt zur Syntax.
Im Allgemeinen verfügen wir über 2 Schlüsselwörter, mit denen Werttypen deklariert werden:
- Structs
- Aufzählungen
Referenztypen
Referenztypen sind etwas komplexer. Referenztypen sind traditionelle Objekte im Sinne der objektorientierten Programmierung. Sie unterstützen also die Vererbung (und deren Vorteile) und unterstützen Finalisierer.
In C # haben wir im Allgemeinen folgende Referenztypen:
- Klassen
- Delegierte
- Schnittstellen
Neue Referenztypen (Klassen) werden mit dem Schlüsselwort class deklariert. Ein Beispiel finden Sie im Abschnitt zur Syntax, wie Sie einen neuen Referenztyp deklarieren.
Hauptunterschiede
Die wichtigsten Unterschiede zwischen Referenztypen und Werttypen sind unten zu sehen.
Werttypen sind auf dem Stack vorhanden, Referenztypen auf dem Heap
Dies ist der oft erwähnte Unterschied zwischen den beiden, aber eigentlich geht es darum, dass das Programm bei Verwendung eines Werttyps in C # wie einem int diese Variable verwendet, um sich direkt auf diesen Wert zu beziehen. Wenn Sie int mein = 0 sagen, bezieht sich die Variable mein direkt auf 0, was effizient ist. Referenztypen enthalten jedoch tatsächlich (wie der Name schon sagt) einen Verweis auf das zugrunde liegende Objekt. Dies ist vergleichbar mit Zeigern in anderen Sprachen wie C ++.
Sie werden die Auswirkungen möglicherweise nicht sofort bemerken, aber die Auswirkungen sind vorhanden, sind mächtig und subtil. Ein Beispiel finden Sie im Beispiel zum Ändern von Referenztypen an anderer Stelle.
Dieser Unterschied ist der Hauptgrund für die folgenden anderen Unterschiede und ist wissenswert.
Werttypen ändern sich nicht, wenn Sie sie in einer Methode ändern, Referenztypen
Wenn ein Werttyp als Parameter an eine Methode übergeben wird, ändert sich der Wert in keiner Weise durch die Methode. Der Wert wird jedoch nicht geändert. Wenn Sie jedoch einen Referenztyp an dieselbe Methode übergeben und ändern, ändert sich das zugrunde liegende Objekt Für andere Dinge, die dasselbe Objekt verwenden, wird das neu geänderte Objekt anstelle des ursprünglichen Werts angezeigt.
Weitere Informationen finden Sie im Beispiel für Werttypen und Referenztypen in Methoden.
Was ist, wenn ich sie ändern möchte?Übergeben Sie sie einfach mit dem Schlüsselwort "ref" an Ihre Methode, und Sie übergeben dieses Objekt als Referenz. Das heißt, es ist das gleiche Objekt im Gedächtnis. Die von Ihnen vorgenommenen Änderungen werden daher respektiert. Ein Beispiel finden Sie im Beispiel zur Referenzübergabe.
Werttypen können nicht null sein, Referenztypen können dies tun
So wie es heißt, können Sie einem Referenztyp den Wert null zuweisen. Dies bedeutet, dass der Variablen, die Sie zugewiesen haben, kein Objekt zugewiesen werden kann. Bei Werttypen ist dies jedoch nicht möglich. Sie können jedoch Nullable verwenden, um zuzulassen, dass Ihr Werttyp auf Null gesetzt werden kann. Wenn dies eine Anforderung ist, sollten Sie jedoch bei starkem Nachdenken stark darüber nachdenken, ob eine Klasse hier möglicherweise nicht der beste Ansatz ist, wenn es Ihre eigene ist Art.
Werte an anderer Stelle ändern
public static void Main(string[] args)
{
var studentList = new List<Student>();
studentList.Add(new Student("Scott", "Nuke"));
studentList.Add(new Student("Vincent", "King"));
studentList.Add(new Student("Craig", "Bertt"));
// make a separate list to print out later
var printingList = studentList; // this is a new list object, but holding the same student objects inside it
// oops, we've noticed typos in the names, so we fix those
studentList[0].LastName = "Duke";
studentList[1].LastName = "Kong";
studentList[2].LastName = "Brett";
// okay, we now print the list
PrintPrintingList(printingList);
}
private static void PrintPrintingList(List<Student> students)
{
foreach (Student student in students)
{
Console.WriteLine(string.Format("{0} {1}", student.FirstName, student.LastName));
}
}
Sie werden feststellen, dass obwohl die Liste der Drucklisten vor den Korrekturen der Schülernamen nach dem Tippfehler erstellt wurde, die Methode PrintPrintingList immer noch die korrigierten Namen ausgibt:
Scott Duke
Vincent Kong
Craig Brett
Dies liegt daran, dass beide Listen eine Liste von Verweisen auf dieselben Schüler enthalten. Das Ändern des zugrundeliegenden Studentenobjekts führt zu einer Verwendung durch eine der beiden Listen.
So würde die Studentenklasse aussehen.
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Student(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
Übergabe als Referenz
Wenn das Beispiel für Wertetypen im Vergleich zu Referenztypen ordnungsgemäß funktionieren soll, verwenden Sie das Schlüsselwort ref in Ihrer Methodensignatur für den Parameter, den Sie als Referenz übergeben möchten, sowie beim Aufrufen der Methode.
public static void Main(string[] args)
{
...
DoubleNumber(ref number); // calling code
Console.WriteLine(number); // outputs 8
...
}
public void DoubleNumber(ref int number)
{
number += number;
}
Wenn Sie diese Änderungen vornehmen, wird die Anzahl wie erwartet aktualisiert. Dies bedeutet, dass die Konsolenausgabe für number 8 wäre.
Übergabe als Referenz mit dem Schlüsselwort ref.
Aus der Dokumentation :
In C # können Argumente entweder per Wert oder als Referenz an Parameter übergeben werden. Durch das Übergeben als Referenz können Funktionsmitglieder, Methoden, Eigenschaften, Indexer, Operatoren und Konstruktoren den Wert der Parameter ändern und diese Änderung in der aufrufenden Umgebung beibehalten. Um einen Parameter als Referenz zu übergeben, verwenden Sie das Schlüsselwort
ref
oderout
.
Der Unterschied zwischen ref
und out
ist , dass out
bedeutet , dass der übergebene Parameter zugeordnet werden muss , bevor die Funktion ends.in Kontrastparameter mit geben ref
geändert werden oder unverändert gelassen.
using System;
class Program
{
static void Main(string[] args)
{
int a = 20;
Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
Callee(a);
Console.WriteLine("Inside Main - After Callee: a = {0}", a);
Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
CalleeRef(ref a);
Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
CalleeOut(out a);
Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
Console.ReadLine();
}
static void Callee(int a)
{
a = 5;
Console.WriteLine("Inside Callee a : {0}", a);
}
static void CalleeRef(ref int a)
{
a = 6;
Console.WriteLine("Inside CalleeRef a : {0}", a);
}
static void CalleeOut(out int a)
{
a = 7;
Console.WriteLine("Inside CalleeOut a : {0}", a);
}
}
Ausgabe :
Inside Main - Before Callee: a = 20
Inside Callee a : 5
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 6
Inside Main - After CalleeRef: a = 6
Inside Main - Before CalleeOut: a = 6
Inside CalleeOut a : 7
Inside Main - After CalleeOut: a = 7
Zuordnung
var a = new List<int>();
var b = a;
a.Add(5);
Console.WriteLine(a.Count); // prints 1
Console.WriteLine(b.Count); // prints 1 as well
Durch Zuweisen einer Variablen einer List<int>
wird keine Kopie der List<int>
. Stattdessen wird der Verweis in die List<int>
kopiert. Wir nennen Typen, die sich als Referenztypen verhalten.
Unterschied bei den Methodenparametern ref und out
Es gibt zwei Möglichkeiten, einen Werttyp als Referenz zu übergeben: ref
und out
. Der Unterschied besteht darin, dass der Wert durch Übergeben mit ref
initialisiert werden muss, nicht jedoch, wenn er nicht mitgegeben out
. Durch out
Verwendung von out
sichergestellt, dass die Variable nach dem Methodenaufruf einen Wert hat:
public void ByRef(ref int value)
{
Console.WriteLine(nameof(ByRef) + value);
value += 4;
Console.WriteLine(nameof(ByRef) + value);
}
public void ByOut(out int value)
{
value += 4 // CS0269: Use of unassigned out parameter `value'
Console.WriteLine(nameof(ByOut) + value); // CS0269: Use of unassigned out parameter `value'
value = 4;
Console.WriteLine(nameof(ByOut) + value);
}
public void TestOut()
{
int outValue1;
ByOut(out outValue1); // prints 4
int outValue2 = 10; // does not make any sense for out
ByOut(out outValue2); // prints 4
}
public void TestRef()
{
int refValue1;
ByRef(ref refValue1); // S0165 Use of unassigned local variable 'refValue'
int refValue2 = 0;
ByRef(ref refValue2); // prints 0 and 4
int refValue3 = 10;
ByRef(ref refValue3); // prints 10 and 14
}
Der Haken ist, dass der Parameter durch Verwendung von out
initialisiert werden must
, bevor die Methode verlassen wird. Daher ist die folgende Methode mit ref
jedoch nicht ohne out
:
public void EmtyRef(bool condition, ref int value)
{
if (condition)
{
value += 10;
}
}
public void EmtyOut(bool condition, out int value)
{
if (condition)
{
value = 10;
}
} //CS0177: The out parameter 'value' must be assigned before control leaves the current method
Dies liegt daran, dass der value
nicht zugewiesen wird, wenn die condition
nicht gilt.
ref vs out-Parameter
Code
class Program
{
static void Main(string[] args)
{
int a = 20;
Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
Callee(a);
Console.WriteLine("Inside Main - After Callee: a = {0}", a);
Console.WriteLine();
Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
CalleeRef(ref a);
Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
Console.WriteLine();
Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
CalleeOut(out a);
Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
Console.ReadLine();
}
static void Callee(int a)
{
a += 5;
Console.WriteLine("Inside Callee a : {0}", a);
}
static void CalleeRef(ref int a)
{
a += 10;
Console.WriteLine("Inside CalleeRef a : {0}", a);
}
static void CalleeOut(out int a)
{
// can't use a+=15 since for this method 'a' is not intialized only declared in the method declaration
a = 25; //has to be initialized
Console.WriteLine("Inside CalleeOut a : {0}", a);
}
}
Ausgabe
Inside Main - Before Callee: a = 20
Inside Callee a : 25
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 30
Inside Main - After CalleeRef: a = 30
Inside Main - Before CalleeOut: a = 30
Inside CalleeOut a : 25
Inside Main - After CalleeOut: a = 25