C# Language
Värdetyp jämfört med referenstypen
Sök…
Syntax
- Skickas vid referens: public void Double (ref int numberToDouble) {}
Anmärkningar
Introduktion
Värde typer
Värdetyper är de enklaste av de två. Värdetyper används ofta för att representera data själv. Ett heltal, en boolesisk eller en punkt i 3D-rymden är alla exempel på goda värdetyper.
Värdetyper (strukturer) deklareras med hjälp av struktursökordet. Se syntaxavsnittet för ett exempel på hur man deklarerar en ny struktur.
Generellt sett har vi två nyckelord som används för att förklara värdetyper:
- structs
- uppräkningar
Referenstyper
Referenstyper är något mer komplexa. Referenstyper är traditionella objekt i betydelsen Objektorienterad programmering. Så de stödjer arv (och fördelarna med det) och stöder även slutförare.
I C # har vi vanligtvis dessa referenstyper:
- Klasser
- delegater
- gränssnitt
Nya referenstyper (klasser) deklareras med hjälp av klassnyckelordet. För exempel, se syntaxavsnittet för hur du deklarerar en ny referenstyp.
Stora skillnader
De största skillnaderna mellan referenstyper och värdetyper kan ses nedan.
Värdetyper finns på stacken, referenstyper finns på högen
Det här är den ofta nämnda skillnaden mellan de två, men egentligen, vad det lägger på är att när du använder en värdetyp i C #, t.ex. en int, kommer programmet att använda den variabeln för att direkt hänvisa till det värdet. Om du säger int mine = 0, refererar den variabla gruvan direkt till 0, vilket är effektivt. Men referenstyper har faktiskt (som namnet antyder) en hänvisning till det underliggande objektet, detta är liknar pekare på andra språk som C ++.
Du kanske inte märker effekterna av detta omedelbart, men effekterna är där, är kraftfulla och är subtila. Se exemplet om att ändra referenstyper någon annanstans för ett exempel.
Denna skillnad är det främsta skälet till följande andra skillnader och är värt att veta.
Värdetyper förändras inte när du ändrar dem i en metod, referenstyper gör det
När en värdetyp överförs till en metod som en parameter, om metoden ändrar värdet på något sätt, ändras inte värdet. Däremot ändras en referenstyp till samma metod och ändrar den underliggande objekt, så att andra saker som använder samma objekt kommer att ha det nyligen ändrade objektet snarare än det ursprungliga värdet.
Se exemplet på värdetyper kontra referenstyper i metoder för mer information.
Tänk om jag vill ändra dem?Ange bara dem till din metod med hjälp av sökordet "ref", och du skickar sedan det här objektet med referens. Vilket innebär att det är samma objekt i minnet. Så ändringar du gör kommer att respekteras. Se exemplet när du passerar med referens för ett exempel.
Värdetyper kan inte vara noll, referenstyper kan
Ganska mycket som det säger, kan du tilldela noll till en referenstyp, vilket betyder att variabeln du har tilldelat kan inte ha något faktiskt objekt tilldelat det. När det gäller värdetyper är detta emellertid inte möjligt. Du kan dock använda Nullable för att låta din värdetyp vara nullable, om detta är ett krav, men om detta är något du funderar på, tänk starkt på om en klass kanske inte är den bästa metoden här, om det är din egen typ.
Ändra värden någon annanstans
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));
}
}
Du kommer att märka att även om utskrivningslistlistan gjordes före korrigeringarna av studentnamnen efter skrivfel, skriver PrintPrintingList-metoden fortfarande ut de korrigerade namnen:
Scott Duke
Vincent Kong
Craig Brett
Detta beror på att båda listorna har en lista med referenser till samma studenter. Så att ändra det underliggande studentobjektet förutsätter användningar genom endera listan.
Så här ser studentklass ut.
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;
}
}
Skickas genom referens
Om du vill att värdetyper kontra referenstyper i metodexempel ska fungera ordentligt använder du sökordet ref i din metodsignatur för den parameter du vill skicka genom referens, liksom när du anropar metoden.
public static void Main(string[] args)
{
...
DoubleNumber(ref number); // calling code
Console.WriteLine(number); // outputs 8
...
}
public void DoubleNumber(ref int number)
{
number += number;
}
Att göra dessa ändringar skulle göra att numret uppdateras som förväntat, vilket innebär att konsolutgången för nummer skulle vara 8.
Vidarebefordras med referens med hjälp av ref-nyckelord.
Från dokumentationen :
I C # kan argument överföras till parametrar antingen efter värde eller genom referens. Genom att gå genom referens kan funktionsmedlemmar, metoder, egenskaper, indexerare, operatörer och konstruktörer ändra värdet på parametrarna och få den ändringen att kvarstå i samtalsmiljön. För att passera en parameter som referens använder
ref
ellerout
sökord.
Skillnaden mellan ref
och out
är att out
betyder att den passerade parametern måste tilldelas innan funktionen slutar. I kontrastparametrar som passeras med ref
kan ändras eller lämnas oförändrade.
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);
}
}
Utgång :
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
Uppdrag
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
Tilldelning till en variabel i en List<int>
skapar inte en kopia av List<int>
. Istället kopierar den referensen till List<int>
. Vi kallar typer som uppför sig på detta sätt referenstyper .
Skillnad med metodparametrar ref och out
Det finns två möjliga sätt att skicka en värdetyp genom referens: ref
och out
. Skillnaden är att genom att skicka det med ref
måste värdet initialiseras men inte när det skickas out
. Använda out
säkerställer att variabeln har ett värde efter metodanrop:
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
}
Fångsten är att med hjälp av out
parametern must
initieras innan de lämnar metoden, följande metod är därför möjligt med ref
men inte med 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
Detta beror på att om condition
inte håller, går value
tilldelat.
ref vs out-parametrar
Koda
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);
}
}
Produktion
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