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 eller out 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


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow