Ricerca…


Sintassi

  • Passando per riferimento: public void Double (ref int numberToDouble) {}

Osservazioni

introduzione

Tipi di valore

I tipi di valore sono i più semplici dei due. I tipi di valore sono spesso usati per rappresentare i dati stessi. Un numero intero, un punto booleano o un punto nello spazio 3D sono tutti esempi di buoni valori.

I tipi di valore (structs) sono dichiarati usando la parola chiave struct. Vedere la sezione della sintassi per un esempio su come dichiarare una nuova struttura.

In generale, abbiamo 2 parole chiave che vengono utilizzate per dichiarare i tipi di valore:

  • Structs
  • enumerazioni

Tipi di riferimento

I tipi di riferimento sono leggermente più complessi. I tipi di riferimento sono oggetti tradizionali nel senso della programmazione orientata agli oggetti. Quindi, supportano l'ereditarietà (e i benefici che ne derivano) e supportano anche i finalizzatori.

In C # generalmente abbiamo questo tipo di riferimento:

  • Classi
  • I delegati
  • interfacce

Nuovi tipi di riferimento (classi) sono dichiarati usando la parola chiave class. Per un esempio, vedere la sezione della sintassi su come dichiarare un nuovo tipo di riferimento.

Major Differences

Di seguito sono riportate le principali differenze tra tipi di riferimento e tipi di valore.

Esistono tipi di valore nello stack, esistono tipi di riferimento sull'heap

Questa è la differenza spesso menzionata tra i due, ma in realtà, ciò a cui si riduce è che quando si utilizza un tipo di valore in C #, ad esempio un int, il programma utilizzerà quella variabile per fare riferimento direttamente a quel valore. Se dici int mine = 0, la variabile mine si riferisce direttamente a 0, che è efficiente. Tuttavia, i tipi di riferimento effettivamente contengono (come suggerisce il nome) un riferimento all'oggetto sottostante, questo è simile ai puntatori in altri linguaggi come C ++.

Potresti non notare immediatamente gli effetti di questo, ma gli effetti sono lì, sono potenti e sono sottili. Vedere l'esempio sulla modifica dei tipi di riferimento altrove per un esempio.

Questa differenza è la ragione principale per le seguenti altre differenze, e vale la pena conoscere.

I tipi di valore non cambiano quando li si cambia in un metodo, i tipi di riferimento lo fanno

Quando un tipo di valore viene passato in un metodo come parametro, se il metodo modifica il valore in qualsiasi modo, il valore non viene modificato Al contrario, passando un tipo di riferimento nello stesso metodo e cambiandolo si cambia l'oggetto sottostante, in modo che altre cose che usano lo stesso oggetto avranno l'oggetto appena cambiato piuttosto che il loro valore originale.

Vedi l'esempio dei tipi di valore rispetto ai tipi di riferimento nei metodi per maggiori informazioni.

Cosa succede se voglio cambiarli?

Semplicemente passali nel tuo metodo usando la parola chiave "ref", e quindi stai passando questo oggetto per riferimento. Significato, è lo stesso oggetto in memoria. Quindi le modifiche che apporti saranno rispettate. Vedere l'esempio sul passaggio per riferimento per un esempio.

I tipi di valore non possono essere nulli, i tipi di riferimento possono

Praticamente come si dice, è possibile assegnare un valore nullo a un tipo di riferimento, nel senso che alla variabile assegnata non può essere assegnato alcun oggetto reale. Nel caso di tipi di valore, tuttavia, ciò non è possibile. Tuttavia, puoi usare Nullable, per consentire al tuo tipo di valore di essere annullabile, se questo è un requisito, anche se questo è qualcosa che stai considerando, pensa fortemente se una classe potrebbe non essere l'approccio migliore qui, se è il tuo genere.

Modifica dei valori altrove

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

Si noterà che anche se l'elenco delle liste di stampa è stato creato prima delle correzioni ai nomi degli studenti dopo gli errori di battitura, il metodo PrintPrintingList stampa ancora i nomi corretti:

Scott Duke
Vincent Kong
Craig Brett

Questo perché entrambe le liste contengono un elenco di riferimenti agli stessi studenti. COSÌ, cambiando l'oggetto studente sottostante si propaga agli usi da entrambe le liste.

Ecco come sarà la classe studentesca.

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

Passando per riferimento

Se si desidera che i tipi di valore e i tipi di riferimento nell'esempio di metodi funzionino correttamente, utilizzare la parola chiave ref nella firma del metodo per il parametro che si desidera passare per riferimento, nonché quando si chiama il metodo.

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

Apportare queste modifiche renderebbe il numero di aggiornamento come previsto, il che significa che l'output della console per il numero sarebbe 8.

Passando per riferimento usando la parola chiave ref.

Dalla documentazione :

In C #, gli argomenti possono essere passati ai parametri per valore o per riferimento. Il passaggio per riferimento abilita membri di funzioni, metodi, proprietà, indicizzatori, operatori e costruttori per modificare il valore dei parametri e mantenere tale modifica nell'ambiente di chiamata. Per passare un parametro per riferimento, utilizzare il ref o out parola chiave.

La differenza tra ref e out è che out significa che il parametro passato deve essere assegnato prima della fine della funzione. I parametri di contrasto passati con ref possono essere modificati o lasciati invariati.

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

Uscita :

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

assegnazione

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

Assegnare a una variabile di una List<int> non crea una copia della List<int> . Invece, copia il riferimento alla List<int> . Chiamiamo tipi che si comportano in questo modo tipi di riferimento .

Differenza con i parametri del metodo ref e out

Esistono due modi possibili per passare un tipo di valore per riferimento: ref e out . La differenza è che passandolo con ref il valore deve essere inizializzato ma non quando lo si passa out . L'utilizzo di out assicura che la variabile abbia un valore dopo la chiamata al metodo:

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
}

Il problema è che utilizzando out il parametro must essere inizializzato prima di lasciare il metodo, quindi il seguente metodo è possibile con ref ma non con 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

Questo perché se la condition non regge, il value non è assegnato.

parametri ref vs out

Codice

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

Produzione

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow