reference new - Tipo di riferimento stringa C#?




comando visual (8)

Come altri hanno affermato, il tipo String in .NET è immutabile e il suo riferimento viene passato per valore.

Nel codice originale, non appena questa riga viene eseguita:

test = "after passing";

quindi test non si riferisce più all'oggetto originale. Abbiamo creato un nuovo oggetto String e assegnato test per fare riferimento a tale oggetto nell'heap gestito.

Sento che molte persone sono inciampate qui perché non c'è alcun costruttore formale visibile a ricordarle. In questo caso, sta succedendo dietro le quinte dal momento che il tipo String ha supporto linguistico nel modo in cui è costruito.

Quindi, questo è il motivo per cui la modifica al test non è visibile al di fuori dell'ambito del metodo TestI(string) - abbiamo passato il riferimento per valore e ora quel valore è cambiato! Ma se il riferimento String stato passato per riferimento, quando il riferimento è cambiato lo vedremo al di fuori dell'ambito del metodo TestI(string) .

In questo caso è necessaria la parola chiave ref o out . Sento che la parola chiave out potrebbe essere leggermente più adatta per questa particolare situazione.

class Program
{
    static void Main(string[] args)
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(out test);
        Console.WriteLine(test);
        Console.ReadLine();
    }

    public static void TestI(out string test)
    {
        test = "after passing";
    }
}

So che "string" in C # è un tipo di riferimento. Questo è su MSDN. Tuttavia, questo codice non funziona come dovrebbe allora:

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(string test)
    {
        test = "after passing";
    }
}

L'output dovrebbe essere "prima di passare" "dopo aver superato" poiché sto passando la stringa come parametro e trattandosi di un tipo di riferimento, la seconda istruzione di output dovrebbe riconoscere che il testo è cambiato nel metodo TestI. Tuttavia, ottengo "prima di passare" "prima di passare", facendo sembrare che sia passato per valore non da ref. Capisco che le stringhe siano immutabili, ma non vedo come ciò spiegherebbe cosa sta succedendo qui. Cosa mi manca? Grazie.


Credo che il tuo codice sia analogo al seguente e non dovresti aspettarti che il valore sia cambiato per lo stesso motivo per cui non sarebbe stato qui:

 public static void Main()
 {
     StringWrapper testVariable = new StringWrapper("before passing");
     Console.WriteLine(testVariable);
     TestI(testVariable);
     Console.WriteLine(testVariable);
 }

 public static void TestI(StringWrapper testParameter)
 {
     testParameter = new StringWrapper("after passing");

     // this will change the object that testParameter is pointing/referring
     // to but it doesn't change testVariable unless you use a reference
     // parameter as indicated in other answers
 }

Il riferimento alla stringa viene passato per valore. C'è una grande differenza tra il passaggio di un riferimento per valore e il passaggio di un oggetto per riferimento. È spiacevole che la parola "riferimento" sia usata in entrambi i casi.

Se si passa la stringa di riferimento per riferimento, funzionerà come previsto:

using System;

class Test
{
    public static void Main()
    {
        string test = "before passing";
        Console.WriteLine(test);
        TestI(ref test);
        Console.WriteLine(test);
    }

    public static void TestI(ref string test)
    {
        test = "after passing";
    }
}

Ora è necessario distinguere tra apportare modifiche all'oggetto a cui fa riferimento un riferimento e apportare una modifica a una variabile (come un parametro) per fare in modo che faccia riferimento a un oggetto diverso. Non possiamo apportare modifiche a una stringa perché le stringhe sono immutabili, ma possiamo invece dimostrarle con un StringBuilder :

using System;
using System.Text;

class Test
{
    public static void Main()
    {
        StringBuilder test = new StringBuilder();
        Console.WriteLine(test);
        TestI(test);
        Console.WriteLine(test);
    }

    public static void TestI(StringBuilder test)
    {
        // Note that we're not changing the value
        // of the "test" parameter - we're changing
        // the data in the object it's referring to
        test.Append("changing");
    }
}

Vedi il mio articolo sul passaggio dei parametri per maggiori dettagli.


In realtà sarebbe stato lo stesso per qualsiasi oggetto, per esempio essere un tipo di riferimento e passare per riferimento sono 2 cose diverse in c #.

Questo funzionerebbe, ma ciò vale indipendentemente dal tipo:

public static void TestI(ref string test)

Anche riguardo alla stringa come tipo di riferimento, è anche speciale. È progettato per essere immutabile, quindi tutti i suoi metodi non modificheranno l'istanza (ne restituiscono una nuova). Ha anche alcune cose extra in esso per le prestazioni.


Se dobbiamo rispondere alla domanda: String è un tipo di riferimento e si comporta come un riferimento. Passiamo un parametro che contiene un riferimento a, non la stringa effettiva. Il problema è nella funzione:

public static void TestI(string test)
{
    test = "after passing";
}

Il test parametri contiene un riferimento alla stringa ma è una copia. Abbiamo due variabili che puntano alla stringa. E poiché ogni operazione con le stringhe crea effettivamente un nuovo oggetto, facciamo in modo che la nostra copia locale faccia riferimento alla nuova stringa. Ma la variabile di test originale non è cambiata.

Le soluzioni suggerite per mettere ref nella dichiarazione di funzione e nell'invocazione funzionano perché non passeremo il valore della variabile di test ma passeremo solo un riferimento ad esso. Pertanto qualsiasi modifica all'interno della funzione rifletterà la variabile originale.

Voglio ripetere alla fine: String è un tipo di riferimento ma dal momento che è immutabile la riga test = "after passing"; crea effettivamente un nuovo oggetto e la copia esterna della variabile test viene cambiata in modo che punti alla nuova stringa.


Ecco un buon modo per pensare alla differenza tra tipi di valore, passaggio per valore, tipi di riferimento e passaggio per riferimento:

Una variabile è un contenitore.

Una variabile di tipo valore contiene un'istanza. Una variabile di tipo riferimento contiene un puntatore a un'istanza memorizzata altrove.

La modifica di una variabile di tipo valore modifica l'istanza che contiene. La modifica di una variabile di tipo riferimento muta l'istanza a cui punta.

Le variabili di tipo di riferimento separate possono puntare alla stessa istanza. Pertanto, la stessa istanza può essere mutata tramite qualsiasi variabile che punta ad essa.

Un argomento passato per valore è un nuovo contenitore con una nuova copia del contenuto. Un argomento passato per riferimento è il contenitore originale con il suo contenuto originale.

Quando un argomento di tipo valore viene passato per valore: la riassegnazione del contenuto dell'argomento non ha alcun effetto al di fuori dell'ambito, poiché il contenitore è univoco. La modifica dell'argomento non ha alcun effetto al di fuori dell'ambito, poiché l'istanza è una copia indipendente.

Quando un argomento di tipo di riferimento viene passato per valore: la riassegnazione del contenuto dell'argomento non ha alcun effetto al di fuori dell'ambito, poiché il contenitore è univoco. La modifica del contenuto dell'argomento influisce sull'ambito esterno, poiché il puntatore copiato punta a un'istanza condivisa.

Quando un argomento viene passato per riferimento: la riassegnazione del contenuto dell'argomento influisce sull'ambito esterno, poiché il contenitore è condiviso. La modifica del contenuto dell'argomento influisce sull'ambito esterno, poiché il contenuto è condiviso.

In conclusione:

Una variabile stringa è una variabile di tipo riferimento. Pertanto, contiene un puntatore a un'istanza memorizzata altrove. Quando viene passato per valore, il suo puntatore viene copiato, quindi la modifica di un argomento stringa dovrebbe influire sull'istanza condivisa. Tuttavia, un'istanza di stringa non ha proprietà mutabili, quindi un argomento di stringa non può essere modificato comunque. Quando viene passato per riferimento, il contenitore del puntatore viene condiviso, quindi la riassegnazione avrà comunque effetto sull'ambito esterno.


Le risposte sopra sono utili, vorrei solo aggiungere un esempio che penso dimostri chiaramente cosa succede quando passiamo il parametro senza la parola chiave ref, anche quando quel parametro è un tipo di riferimento:

MyClass c = new MyClass(); c.MyProperty = "foo";

CNull(c); // only a copy of the reference is sent 
Console.WriteLine(c.MyProperty); // still foo, we only made the copy null
CPropertyChange(c); 
Console.WriteLine(c.MyProperty); // bar


private void CNull(MyClass c2)
        {          
            c2 = null;
        }
private void CPropertyChange(MyClass c2) 
        {
            c2.MyProperty = "bar"; // c2 is a copy, but it refers to the same object that c does (on heap) and modified property would appear on c.MyProperty as well.
        }

Un semplice trucco che uso normalmente è quello di racchiuderlo in una lista:

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

(Sì, so che questo può essere sconveniente, ma a volte è abbastanza semplice farlo).





c# string reference types