Szukaj…


Składnia

  • Przekazywanie przez odniesienie: public void Double (ref int numberToDouble) {}

Uwagi

Wprowadzenie

Typy wartości

Typy wartości są prostsze z tych dwóch. Typy wartości są często używane do reprezentowania samych danych. Liczba całkowita, wartość logiczna lub punkt w przestrzeni 3D to przykłady dobrych typów wartości.

Typy wartości (struktury) deklarowane są za pomocą słowa kluczowego struct. Zobacz sekcję dotyczącą składni, aby zobaczyć, jak zadeklarować nową strukturę.

Ogólnie rzecz biorąc, mamy 2 słowa kluczowe, które służą do deklarowania typów wartości:

  • Struktury
  • Wyliczenia

Typy referencyjne

Typy referencyjne są nieco bardziej złożone. Typy referencyjne są tradycyjnymi obiektami w znaczeniu programowania obiektowego. Wspierają więc dziedziczenie (i wynikające z niego korzyści), a także wspierają finalizatorów.

W języku C # ogólnie mamy następujące typy referencji:

  • Klasy
  • Delegaci
  • Interfejsy

Nowe typy referencyjne (klasy) deklaruje się za pomocą słowa kluczowego class. Na przykład zobacz sekcję składni, aby dowiedzieć się, jak zadeklarować nowy typ odwołania.

Główne różnice

Główne różnice między typami odniesienia i typami wartości można zobaczyć poniżej.

Na stosie istnieją typy wartości, na stercie typy referencji

Jest to często wspomniana różnica między tymi dwoma, ale tak naprawdę sprowadza się to do tego, że gdy użyjesz typu wartości w C #, takiego jak int, program użyje tej zmiennej, aby odnieść się bezpośrednio do tej wartości. Jeśli powiesz int mine = 0, to zmienna mine odnosi się bezpośrednio do 0, co jest wydajne. Jednak typy referencji faktycznie zawierają (jak sama nazwa wskazuje) odwołanie do obiektu bazowego, jest to podobne do wskaźników w innych językach, takich jak C ++.

Możesz nie zauważyć efektów tego natychmiast, ale efekty są, są potężne i subtelne. Zobacz przykład zmiany typów referencji w innym miejscu.

Ta różnica jest głównym powodem następujących innych różnic i warto ją poznać.

Typy wartości nie zmieniają się, gdy zmieniasz je w metodzie, podobnie jak typy referencyjne

Gdy typ wartości jest przekazywany do metody jako parametr, jeśli metoda w jakikolwiek sposób zmienia wartość, wartość nie ulega zmianie. Natomiast przekazanie typu referencyjnego do tej samej metody i zmiana go spowoduje zmianę obiektu bazowego, dzięki czemu inne rzeczy korzystające z tego samego obiektu będą miały nowo zmieniony obiekt, a nie jego oryginalną wartość.

Zobacz przykład typów wartości vs typy referencyjne w metodach, aby uzyskać więcej informacji.

Co jeśli chcę je zmienić?

Po prostu przekaż je do metody za pomocą słowa kluczowego „ref”, a następnie przekaż ten obiekt przez odniesienie. Czyli to ten sam obiekt w pamięci. Wprowadzane modyfikacje będą przestrzegane. Zobacz przykład przechodzenia przez odniesienie na przykład.

Typy wartości nie mogą mieć wartości null, typy referencyjne mogą

W zasadzie, jak mówi, możesz przypisać null do typu odniesienia, co oznacza, że do przypisanej zmiennej nie można przypisać żadnego rzeczywistego obiektu. W przypadku typów wartości nie jest to jednak możliwe. Możesz jednak użyć wartości Nullable, aby zezwolić na określenie wartości typu nullable, jeśli jest to wymóg, ale jeśli rozważasz to, zastanów się, czy klasa nie jest najlepszym podejściem, jeśli jest to Twoja własność rodzaj.

Zmieniamy wartości gdzie indziej

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));
    }
}

Zauważysz, że chociaż lista printList została utworzona przed poprawkami nazwisk uczniów po literówkach, metoda PrintPrintingList nadal drukuje poprawione nazwy:

Scott Duke
Vincent Kong
Craig Brett

Wynika to z faktu, że obie listy zawierają listę odniesień do tych samych studentów. Tak więc zmiana bazowego obiektu ucznia podpowiada użycie według dowolnej listy.

Oto jak wyglądałaby klasa uczniów.

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;
    }
}

Przekazywanie przez odniesienie

Jeśli chcesz, aby przykład Typy wartości a Typy referencyjne w metodach działał poprawnie, użyj słowa kluczowego ref w podpisie metody dla parametru, który chcesz przekazać przez referencję, a także podczas wywoływania metody.

public static void Main(string[] args)
{
    ...
    DoubleNumber(ref number); // calling code
    Console.WriteLine(number); // outputs 8
    ...
}
public void DoubleNumber(ref int number)
{
    number += number;
}

Wprowadzenie tych zmian spowodowałoby, że liczba byłaby aktualizowana zgodnie z oczekiwaniami, co oznacza, że wyjście konsoli dla liczby wynosiłoby 8.

Przekazywanie przez referencję przy użyciu słowa kluczowego ref.

Z dokumentacji :

W języku C # argumenty mogą być przekazywane do parametrów albo przez wartość, albo przez odwołanie. Przekazywanie przez odwołanie umożliwia członom funkcji, metodom, właściwościom, indeksatorom, operatorom i konstruktorom zmianę wartości parametrów i utrzymanie tej zmiany w środowisku wywołującym. Aby przekazać parametr przez odwołanie, użyj słowa kluczowego ref lub out .

Różnica między ref i out polega na tym out że out oznacza, że przekazany parametr musi zostać przypisany przed zakończeniem funkcji. W przeciwieństwie do parametrów przekazanych z ref można zmienić lub pozostawić bez zmian.

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);
    }
}

Wyjście :

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

Zadanie

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

Przypisanie do zmiennej List<int> nie powoduje utworzenia kopii List<int> . Zamiast tego kopiuje odwołanie do List<int> . Nazywamy typy, które zachowują się w ten sposób, typami referencyjnymi .

Różnica między parametrami metody ref i out

Istnieją dwa sposoby przekazania typu wartości przez odwołanie: ref i out . Różnica polega na tym, że przekazując ją z ref wartość należy zainicjować, ale nie przy przekazywaniu jej out . Użycie out zapewnia, że zmienna ma wartość po wywołaniu metody:

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
}

Złap polega na tym, że użycie parametru out must zostać zainicjowane przed opuszczeniem metody, dlatego możliwa jest następująca metoda z ref ale nie 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

Wynika to z faktu, że jeśli condition się nie utrzymuje, value zostaje nieprzypisana.

parametry ref a out

Kod

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);
    }
}

Wynik

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow