Qual è la differenza tra un tipo di riferimento e un tipo di valore in c #?


100

Qualcuno mi ha fatto questa domanda un paio di mesi fa e non sono riuscito a spiegarla in dettaglio. Qual è la differenza tra un tipo di riferimento e un tipo di valore in C #?

So che i tipi di valore sono int, bool, float, ecc e di riferimento sono i tipi delegate, interfaceecc O è questo torto, troppo?

Puoi spiegarmelo in modo professionale?


3
Come piccola nota, penso che la domanda venga posta su C #, ma in realtà si tratta di C # + .NET. Non è possibile analizzare C # senza analizzare .NET. Non ricodificherò la domanda perché potrebbero esserci alcuni punti da sottolineare sull'analisi di uno senza analizzare l'altro (iteratori e chiusure, ti sto guardando)
xanatos

@xanatos è più appropriatamente una domanda sulla CLI che C #, VB.Net e Net hanno tutti in comune. Dovrebbe esserci un tag per la CLI ma la CLI è considerata qualcos'altro. C'è CLR ma questa è un'implementazione, non uno standard.
user34660

Risposte:


172

I suoi esempi sono un po 'strano perché mentre int, boole floatsono specifici tipi, interfacce e delegati sono i tipi di tipo - proprio come structe enumsono i tipi di tipi di valore.

Ho scritto una spiegazione dei tipi di riferimento e dei tipi di valore in questo articolo . Sarei felice di espandere su qualsiasi bit che trovi confuso.

La versione "TL; DR" serve per pensare a quale sia il valore di una variabile / espressione di un particolare tipo. Per un tipo di valore, il valore è l'informazione stessa. Per un tipo di riferimento, il valore è un riferimento che può essere nullo o può essere un modo per navigare verso un oggetto contenente le informazioni.

Ad esempio, pensa a una variabile come a un pezzo di carta. Potrebbe avere il valore "5" o "falso" scritto sopra, ma non potrebbe avere la mia casa ... dovrebbe avere le indicazioni per casa mia. Quelle direzioni sono l'equivalente di un riferimento. In particolare, due persone potrebbero avere fogli di carta diversi contenenti le stesse indicazioni per casa mia - e se una persona seguisse quelle indicazioni e dipingesse la mia casa di rosso, anche la seconda persona vedrebbe quel cambiamento. Se entrambi avessero solo immagini separate della mia casa sulla carta, allora una persona che colorasse la loro carta non cambierebbe affatto la carta dell'altra persona.


2
È importante notare che ci sono tre distinti tipi primari di semantica che una cosa può offrire: semantica immutabile, semantica del valore mutabile e semantica di riferimento mutabile. Concettualmente, il tipo di semantica implementata da una cosa è ortogonale al fatto che sia memorizzato come oggetto heap autonomo o come variabile / campo (struct). In pratica, mentre le strutture che non espongono i loro campi possono implementare qualsiasi tipo di semantica, il fatto che .net consenta la condivisione promiscua dei riferimenti all'heap significa che gli oggetti heap non possono implementare la semantica del valore mutabile.
supercat

Non ho capito questo bit - while int, bool and float are specific types, interfaces and delegates are kinds of type - just like struct and enum are kinds of value types. Cosa intendi con int, bool come tipi specifici? Tutto in C #, ad esempio int, bool, float, class, interface, delegate è un tipo (tipo di dati per essere precisi). I tipi di dati sono separati come "Tipo di riferimento" e "Tipo di valore" in C #. Allora perché stai dicendo che int è un tipo specifico ma l'interfaccia è un file tipo di tipo?
RBT

2
@RBT: i tipi di dati non sono solo separati in "tipo di riferimento" e "tipo di valore". Sono inoltre suddivisi in "class, struct, enum, delegate, interface". intè una struttura, stringè una classe, Actionè un delegato, ecc. La tua lista di "int, bool, float, class, interface, delegate" è una lista che contiene diversi tipi di cose, nello stesso modo in cui "10, int" è un elenco contenente diversi tipi di cose.
Jon Skeet

@ JonSkeet Forse la risposta su questo post è allora un po 'fuorviante.
RBT

@RBT: Direi che è un po 'mal formulato, ma non orribile.
Jon Skeet

26

Tipo di valore:

Contiene un valore non indirizzi di memoria

Esempio:

Struct

Conservazione:

TL; DR : il valore di una variabile viene memorizzato ovunque venga annullato. Le variabili locali vivono nello stack, ad esempio, ma quando dichiarate all'interno di una classe come membro, vivono nell'heap strettamente accoppiate alla classe in cui sono dichiarate.
Più lungo : i tipi di valore vengono quindi memorizzati ovunque siano dichiarati. Ad esempio: intil valore di un all'interno di una funzione come variabile locale verrebbe memorizzato nello stack, mentre intil valore di un in dichiarato come membro di una classe verrebbe memorizzato nell'heap con la classe in cui è dichiarato. Un tipo di valore su una classe ha un tipo di vita che è esattamente lo stesso della classe in cui è dichiarata, e non richiede quasi nessun lavoro da parte del garbage collector. Tuttavia è più complicato, farei riferimento al libro di @ JonSkeet " C # In Depth "Memory in .NET "per una spiegazione più concisa.

Vantaggi:

Un tipo di valore non necessita di ulteriore garbage collection. Ottiene la spazzatura raccolta insieme all'istanza in cui vive. Le variabili locali nei metodi vengono ripulite al termine del metodo.

Svantaggi:

  1. Quando un grande insieme di valori viene passato a un metodo, la variabile ricevente copia effettivamente, quindi ci sono due valori ridondanti in memoria.

  2. Poiché le lezioni vengono perse, perde tutti i vantaggi del personale

Tipo di riferimento:

Contiene un indirizzo di memoria di un valore non valore

Esempio:

Classe

Conservazione:

Memorizzato nel mucchio

Vantaggi:

  1. Quando si passa una variabile di riferimento a un metodo e questa cambia, cambia effettivamente il valore originale mentre nei tipi di valore viene presa una copia della variabile data e questo valore viene modificato.

  2. Quando la dimensione della variabile è maggiore, il tipo di riferimento è buono

  3. Poiché le classi si presentano come variabili di tipo di riferimento, danno riusabilità, a vantaggio della programmazione orientata agli oggetti

Svantaggi:

Più lavoro di riferimento durante l'allocazione e dereferenze durante la lettura dell'overload value.extra per Garbage Collector


5
Non è necessariamente vero che i tipi di riferimento vengono archiviati nell'heap e i tipi di valore vengono archiviati nello stack. Leggi yoda.arachsys.com/csharp/memory.html se vuoi saperne di più.
Rhys

1
Ci sono molti malintesi in questa risposta. Si prega di leggere Jeff Richters CLR tramite C #. I tipi di valore sono archiviati nello stack di thread e non sono soggetti a Garbage Collection (GC): non hanno nulla a che fare con GC. I tipi di riferimento sono archiviati nell'heap gestito e sono pertanto soggetti a GC. Se un Ref Type ha un riferimento alla radice non può essere raccolto ed è promosso alle generazioni, 0, 1 e 2. Se non ha un riferimento alla radice può essere Garbage Collected e quindi passa attraverso questo processo chiamato Resurrection dove si trova viene ucciso e riportato in vita e poi finalmente raccolto.
Jeremy Thompson

13

Ho trovato più facile capire la differenza dei due se sai come il computer alloca le cose in memoria e sai cos'è un puntatore.

Il riferimento è solitamente associato a un puntatore. Significa che l'indirizzo di memoria in cui risiede la variabile contiene effettivamente un altro indirizzo di memoria di dell'oggetto effettivo in una posizione di memoria diversa.

L'esempio che sto per dare è grossolanamente semplificato, quindi prendilo con le pinze.

Immagina che la memoria del computer sia un mucchio di caselle postali in fila (a partire dalla casella postale 0001 fino alla casella postale n) che possono contenere qualcosa al suo interno. Se le caselle postali non lo fanno per te, prova una tabella hash o un dizionario o un array o qualcosa di simile.

Quindi, quando fai qualcosa come:

var a = "Hello";

il computer farà quanto segue:

  1. allocare memoria (diciamo a partire dalla posizione di memoria 1000 per 5 byte) e inserire H (a 1000), e (a 1001), l (a 1002), l (a 1003) e o (a 1004).
  2. allocare da qualche parte in memoria (diciamo nella posizione 0500) e assegnarlo come variabile a.
    Quindi è un po 'come un alias (0500 è un).
  3. assegna il valore in quella posizione di memoria (0500) a 1000 (che è dove la stringa Hello inizia in memoria). Pertanto, la variabile a contiene un riferimento alla posizione di memoria iniziale effettiva della stringa "Hello".

Il tipo di valore manterrà l'oggetto effettivo nella sua posizione di memoria.

Quindi, quando fai qualcosa come:

var a = 1;

il computer farà quanto segue:

  1. alloca una posizione di memoria diciamo 0500 e assegnala alla variabile a (la stessa cosa alias)
  2. inserire il valore 1 (nella posizione di memoria 0500).
    Si noti che non stiamo allocando memoria aggiuntiva per contenere il valore effettivo (1). Quindi a sta effettivamente tenendo il valore effettivo ed è per questo che si chiama tipo di valore.


@ Jon, beh, questo tipo di invalidare quello che stavo dicendo, LOL. Ma come ho detto, è grossolanamente semplificato per ottenere una certa comprensione tra i due tipi che nel mio caso ho trovato utile. Almeno è così che l'ho immaginato nella mia mente :).
Jimmy Chandra

8

Questo viene da un mio post da un forum diverso, circa due anni fa. Sebbene il linguaggio sia vb.net (al contrario di C #), i concetti di tipo di valore e tipo di riferimento sono uniformi in tutto .net e gli esempi continuano a valere.

È anche importante ricordare che all'interno di .net, TUTTI i tipi derivano tecnicamente dal tipo di base Object. I tipi di valore sono progettati per comportarsi come tali, ma alla fine ereditano anche la funzionalità del tipo di base Object.

R. I tipi di valore sono proprio questo: rappresentano un'area distinta della memoria in cui è memorizzato un VALORE discreto. I tipi di valore hanno una dimensione di memoria fissa e sono archiviati nello stack, che è una raccolta di indirizzi di dimensione fissa.

Quando fai una dichiarazione come questa:

Dim A as Integer
DIm B as Integer

A = 3
B = A 

Hai fatto quanto segue:

  1. Creato 2 spazi in memoria sufficienti per contenere valori interi a 32 bit.
  2. Inserito un valore di 3 nell'allocazione di memoria assegnata ad A
  3. Posizionato un valore di 3 nell'allocazione di memoria assegnata a B assegnandogli lo stesso valore di quello tenuto in A.

Il valore di ogni variabile esiste in modo discreto in ogni posizione di memoria.

B. I tipi di riferimento possono essere di varie dimensioni. Pertanto, non possono essere memorizzati nello "Stack" (ricorda, lo stack è una raccolta di allocazioni di memoria di dimensione fissa?). Sono archiviati nel "Managed Heap". I puntatori (o "riferimenti") a ogni elemento nell'heap gestito vengono mantenuti nello stack (come un indirizzo). Il codice utilizza questi puntatori nello stack per accedere agli oggetti archiviati nell'heap gestito. Quindi, quando il codice utilizza una variabile di riferimento, utilizza effettivamente un puntatore (o "indirizzo" a una posizione di memoria nell'heap gestito).

Supponiamo di aver creato una classe denominata clsPerson, con una stringa Property Person.Name

In questo caso, quando fai un'affermazione come questa:

Dim p1 As clsPerson
p1 = New clsPerson
p1.Name = "Jim Morrison"

Dim p2 As Person

p2 = p1

Nel caso precedente, la proprietà p1.Name restituirà "Jim Morrison", come ci si aspetterebbe. La proprietà p2.Name restituirà INOLTRE "Jim Morrison", come ci si aspetterebbe intuitivamente. Credo che sia p1 che p2 rappresentino indirizzi distinti sullo Stack. Tuttavia, ora che hai assegnato a p2 il valore di p1, sia p1 che p2 puntano alla STESSA POSIZIONE sull'heap gestito.

Ora considera QUESTA situazione:

Dim p1 As clsPerson
Dim p2 As clsPerson

p1 = New clsPerson
p1.Name = "Jim Morrison"

p2 = p1

p2.Name = "Janis Joplin"

In questa situazione, hai creato una nuova istanza della classe persona sull'heap gestito con un puntatore p1 sullo Stack che fa riferimento all'oggetto e hai assegnato di nuovo alla proprietà Name dell'istanza dell'oggetto un valore di "Jim Morrison". Successivamente, hai creato un altro puntatore p2 nello Stack e lo hai puntato allo stesso indirizzo sull'heap gestito di quello a cui fa riferimento p1 (quando hai eseguito l'assegnazione p2 = p1).

Ecco il colpo di scena. Quando si assegna la proprietà Assign the Name di p2 al valore "Janis Joplin", si modifica la proprietà Name per l'oggetto REFERENCED sia da p1 che da p2, in modo tale che, se si esegue il codice seguente:

MsgBox(P1.Name)
'Will return "Janis Joplin"

MsgBox(p2.Name)
'will ALSO return "Janis Joplin"Because both variables (Pointers on the Stack) reference the SAME OBJECT in memory (an Address on the Managed Heap). 

Aveva senso?

Scorso. Se lo fai:

DIm p1 As New clsPerson
Dim p2 As New clsPerson

p1.Name = "Jim Morrison"
p2.Name = "Janis Joplin"

Ora hai due oggetti persona distinti. Tuttavia, nel momento in cui lo fai di nuovo:

p2 = p1

Ora hai puntato entrambi di nuovo a "Jim Morrison". (Non sono esattamente sicuro di cosa sia successo all'oggetto sull'heap a cui fa riferimento p2 ... PENSO che ora sia uscito dal campo di applicazione. Questa è una di quelle aree in cui si spera che qualcuno possa rimettermi in sesto ...). -EDIT: Credo che questo sia il motivo per cui dovresti impostare p2 = Nothing OPPURE p2 = New clsPerson prima di fare il nuovo incarico.

Ancora una volta, se ora fai QUESTO:

p2.Name = "Jimi Hendrix"

MsgBox(p1.Name)
MsgBox(p2.Name)

Entrambi i msgBox ora restituiranno "Jimi Hendrix"

Questo può creare confusione per un po ', e dirò un'ultima volta che potrei aver sbagliato alcuni dettagli.

Buona fortuna, e si spera che altri che lo sanno meglio di me verranno per aiutare a chiarire alcune di queste cose. . .


Non so perché non hai ricevuto voti positivi. Buona risposta, mi ha aiutato a capire con esempi chiari e semplici.
Harry,

Per quanto riguarda il tipo di valore e il tipo di riferimento, i concetti sono uniformi in tutto .net, in realtà sono definiti nella specifica Common Language Infrastructure (CLI), lo standard Ecma 335 (anche uno standard ISO). Questo è lo standard per la parte standard di .Net. Lo standard Ecma 334 (anche uno standard ISO) è il linguaggio C # e afferma esplicitamente che le implementazioni C # devono fare affidamento sulla CLI o supportare un modo alternativo per ottenere le funzionalità CLI minime richieste da questo standard C # . VB.Net tuttavia non è uno standard, è proprietario di Microsoft.
user34660

5

tipo di dati valore e tipo di dati di riferimento

1) valore (contiene i dati direttamente) ma riferimento (si riferisce ai dati)

2) in valore (ogni variabile ha la sua copia) ma
in riferimento (più di variabile può riferirsi ad alcuni oggetti)

3) in valore (la variabile operativa non può avere effetto su altre variabili) ma in riferimento (la variabile può influenzare altre)

4) i tipi di valore sono (int, bool, float) ma i tipi di riferimento sono (array, oggetti di classe, stringa)


2

Tipo di valore:

  • Dimensioni della memoria fisse.

  • Archiviato nella memoria Stack.

  • Mantiene il valore effettivo.

    Ex. int, char, bool, ecc ...

Tipo di riferimento:

  • Memoria non fissa.

  • Archiviato nella memoria Heap.

  • Contiene l'indirizzo di memoria del valore effettivo.

    Ex. stringa, array, classe, ecc ...


1

"Le variabili basate sui tipi di valore contengono direttamente i valori. L'assegnazione di una variabile del tipo di valore a un'altra copia il valore contenuto. Ciò differisce dall'assegnazione di variabili del tipo di riferimento, che copia un riferimento all'oggetto ma non l'oggetto stesso." dalla libreria di Microsoft.

Puoi trovare una risposta più completa qui e qui .


1
Non mi piace questa spiegazione, perché sembra che l'assegnazione funzioni in modo diverso per i tipi di riferimento e i tipi di valore. Non è così. In entrambi i casi, rende il valore della variabile "target" uguale all'espressione: il valore viene copiato. La differenza sta in ciò che è quel valore: per i tipi di riferimento, il valore che viene copiato è un riferimento. Questo è comunque il valore della variabile.
Jon Skeet

Sono d'accordo con te e ho già saputo che potrebbe essere diverso, come puoi leggere in questo articolo . Ma sto solo ripassando la guida di Microsoft sull'argomento e anche su come di solito leggi nei libri. Per favore non incolpare me! :)
Lucas S.

Oh certo ... ci sono un sacco di documenti MSDN in cui è possibile trovare difetti :)
Jon Skeet

1

A volte le spiegazioni non aiutano soprattutto per i principianti. Puoi immaginare il tipo di valore come file di dati e il tipo di riferimento come collegamento a un file.

Quindi, se copi una variabile di riferimento, copi solo il collegamento / puntatore a un dato reale da qualche parte nella memoria. Se copi un tipo di valore, cloni davvero i dati in memoria.


0

Questo è probabilmente sbagliato in modi esoterici, ma per semplificare:

I tipi di valore sono valori che vengono passati normalmente "per valore" (quindi copiandoli). I tipi di riferimento vengono passati "per riferimento" (fornendo così un puntatore al valore originale). Non c'è alcuna garanzia da parte dello standard .NET ECMA di dove queste "cose" vengono salvate. Potresti costruire un'implementazione di .NET che è stackless o una che è heapless (la seconda sarebbe molto complessa, ma probabilmente potresti, usando fibre e molti stack)

Le strutture sono tipo valore (int, bool ... sono strutture, o almeno sono simulate come ...), le classi sono tipo riferimento.

I tipi di valore discendono da System.ValueType. Il tipo di riferimento discende da System.Object.

Ora .. Alla fine hai Value Type, "oggetti referenziati" e riferimenti (in C ++ sarebbero chiamati puntatori a oggetti. In .NET sono opachi. Non sappiamo cosa siano. Dal nostro punto di vista essi sono "maniglie" dell'oggetto). Questi ultimi sono simili ai tipi di valore (vengono passati per copia). Quindi un oggetto è composto dall'oggetto (un tipo di riferimento) e zero o più riferimenti ad esso (che sono simili ai tipi di valore). Quando ci sono zero riferimenti, il GC probabilmente lo raccoglierà.

In generale (nell'implementazione "predefinita" di .NET), il tipo di valore può andare sullo stack (se sono campi locali) o sull'heap (se sono campi di una classe, se sono variabili in una funzione iteratore, se sono variabili a cui fa riferimento una chiusura, se sono variabili in una funzione asincrona (utilizzando il più recente CTP Async) ...). Il valore di riferimento può andare solo nell'heap. I riferimenti utilizzano le stesse regole dei tipi di valore.

Nei casi di Value Type che vanno sull'heap perché sono in una funzione iteratore, una funzione asincrona, o sono referenziati da una chiusura, se guardi il file compilato vedrai che il compilatore ha creato una classe per mettere queste variabili e la classe viene creata quando chiami la funzione.

Ora, non so come scrivere cose lunghe e ho cose migliori da fare nella mia vita. Se vuoi una versione "precisa" "accademica" "corretta", leggi QUESTO:

http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx

Sono 15 minuti che lo cerco! È migliore delle versioni di msdn, perché è un articolo condensato "pronto per l'uso".


1
È sbagliato in modi più che esoterici. Direi che è fondamentalmente sbagliato, perché anche i valori del tipo di riferimento sono ancora passati per valore; è solo che il valore è un riferimento, non un oggetto. Vedi pobox.com/~skeet/csharp/parameters.html . Oh, e anche le variabili locali possono finire nell'heap, ad esempio se vengono acquisite o fanno parte di un blocco iteratore.
Jon Skeet

I blocchi iteratori vengono convertiti in classi, quindi "dietro di te" sono "campi di una classe". Lo stesso per le chiusure. Sì ... ho dimenticato di scrivere la distinzione tra il "puntatore" (il riferimento) e il "puntato"
xanatos

@xanatos: certo, sono campi di una classe dopo la compilazione, ma sono ancora variabili locali nel codice sorgente. Inoltre non chiamerei i riferimenti stessi "tipi di valore" - penso di sapere da dove vieni, ma non credo sia una buona idea confondere le acque in questo modo.
Jon Skeet

@jon Sì ... Sono un terzo tipo, perché i puntatori sono "opachi" in .net e non derivano da ValueType. Ma sono più simili ai tipi di valore che ai riferimenti. Puoi "rifarli" e "eliminarli". Ho dovuto fangare le acque perché "qualcuno" doveva controllare il funzionamento degli iteratori.
xanatos

Guardando l'articolo che ora indico, ho scoperto: "Esistono tre tipi di valori: (1) istanze di tipi di valore, (2) istanze di tipi di riferimento e (3) riferimenti. (Il codice in C # non può manipolare istanze di tipi di riferimento direttamente; lo fa sempre tramite un riferimento. Nel codice non sicuro, i tipi di puntatore vengono trattati come tipi di valore allo scopo di determinare i requisiti di archiviazione dei loro valori. ) ".
xanatos

0

Il modo più semplice per pensare ai tipi di riferimento è considerarli come "ID oggetto"; le uniche cose che si possono fare con un ID oggetto sono crearne uno, copiarne uno, indagare o manipolare il tipo di uno, o confrontare due per l'uguaglianza. Un tentativo di fare qualsiasi altra cosa con un ID oggetto sarà considerato come una scorciatoia per eseguire l'azione indicata con l'oggetto a cui fa riferimento quell'ID.

Supponiamo che io abbia due variabili X e Y di tipo Car - un tipo di riferimento. Y contiene "ID oggetto # 19531". Se dico "X = Y", X conterrà "ID oggetto # 19531". Nota che né X né Y hanno una macchina. L'auto, altrimenti nota come "ID oggetto # 19531", è immagazzinata altrove. Quando ho copiato Y in X, tutto ciò che ho fatto è stato copiare il numero ID. Supponiamo ora che io dica X.Color = Colors.Blue. Una tale dichiarazione sarà considerata come un'istruzione per cercare "ID oggetto # 19531" e dipingerlo di blu. Nota che anche se X e Y ora si riferiscono a un'auto blu anziché a una gialla, l'affermazione in realtà non influisce su X o Y, perché entrambi si riferiscono ancora a "ID oggetto # 19531", che è sempre la stessa macchina lo è sempre stato.


0

I tipi di variabili e il valore di riferimento sono facili da applicare e ben applicati al modello di dominio, facilitano il processo di sviluppo.

Per rimuovere qualsiasi mito sulla quantità di "tipo di valore", commenterò come questo viene gestito sulla piattaforma. NET, in particolare in C # (CSharp) quando viene chiamato APIS e invia parametri per valore, per riferimento, nei nostri metodi e funzioni e come eseguire il trattamento corretto dei passaggi di questi valori.

Leggi questo articolo Riferimento e valore del tipo di variabile in C #


Questo è un sito di domande e risposte solo in inglese, sfortunatamente = \. Grazie per aver cercato di rispondere, comunque. Si prega di creare risposte complete, con collegamenti solo come ausili (ma non come risposta sostenuta completa). Dai un'occhiata a come rispondere .
Jesse

0

Supponiamo che vsia un'espressione / variabile di tipo valore e un'espressione / variabile rdi tipo riferimento

    x = v  
    update(v)  //x will not change value. x stores the old value of v

    x = r 
    update(r)  //x now refers to the updated r. x only stored a link to r, 
               //and r can change but the link to it doesn't .

Pertanto, una variabile di tipo valore memorizza il valore effettivo (5 o "h"). Una variabile di tipo riferimento memorizza solo un collegamento a una casella metaforica in cui si trova il valore.


0

Prima di spiegare i diversi tipi di dati disponibili in C #, è importante ricordare che C # è un linguaggio fortemente tipizzato. Ciò significa che ogni variabile, costante, parametro di input, tipo restituito e in generale ogni espressione che restituisce un valore ha un tipo.

Ogni tipo contiene informazioni che verranno incorporate dal compilatore nel file eseguibile come metadati che verranno utilizzati da Common Language Runtime (CLR) per garantire l'indipendenza dai tipi quando alloca e recupera memoria.

Se vuoi sapere quanta memoria alloca un tipo specifico, puoi usare l'operatore sizeof come segue:

static void Main()
{
    var size = sizeof(int);
    Console.WriteLine($"int size:{size}");
    size = sizeof(bool);
    Console.WriteLine($"bool size:{size}");
    size = sizeof(double);
    Console.WriteLine($"double size:{size}");
    size = sizeof(char);
    Console.WriteLine($"char size:{size}");
}

L'output mostrerà il numero di byte allocati da ciascuna variabile.

int size:4
bool size:1
double size:8
char size:2

Le informazioni relative a ciascuna tipologia sono:

  • Lo spazio di archiviazione richiesto.
  • I valori massimo e minimo. Ad esempio, il tipo Int32 accetta valori compresi tra 2147483648 e 2147483647.
  • Il tipo di base da cui eredita.
  • La posizione in cui verrà allocata la memoria per le variabili in fase di esecuzione.
  • I tipi di operazioni consentite.
  • I membri (metodi, campi, eventi e così via) contenuti nel tipo. Ad esempio, se controlliamo la definizione del tipo int, troveremo la struttura e i membri seguenti:

    namespace System
    {
        [ComVisible(true)]
        public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<Int32>, IEquatable<Int32>
        {      
            public const Int32 MaxValue = 2147483647;     
            public const Int32 MinValue = -2147483648;
            public static Int32 Parse(string s, NumberStyles style, IFormatProvider provider);    
            ... 
        }  
    }

Gestione della memoria Quando più processi sono in esecuzione su un sistema operativo e la quantità di RAM non è sufficiente per contenerlo tutto, il sistema operativo mappa parti del disco rigido con la RAM e inizia a memorizzare i dati nel disco rigido. Il sistema operativo utilizzerà tabelle specifiche in cui gli indirizzi virtuali vengono mappati ai corrispondenti indirizzi fisici per eseguire la richiesta. Questa capacità di gestire la memoria è chiamata memoria virtuale.

In ogni processo, la memoria virtuale disponibile è organizzata nelle seguenti 6 sezioni ma per la rilevanza di questo argomento ci concentreremo solo sullo stack e sull'heap.

Stack Lo stack è una struttura dati LIFO (last in, first out), con dimensioni dipendenti dal sistema operativo (per impostazione predefinita, per macchine ARM, x86 e x64 Windows riserva 1 MB, mentre Linux riserva da 2 MB a 8 MB a seconda versione).

Questa sezione di memoria è gestita automaticamente dalla CPU. Ogni volta che una funzione dichiara una nuova variabile, il compilatore alloca un nuovo blocco di memoria grande quanto la sua dimensione nello stack e quando la funzione è finita, il blocco di memoria per la variabile viene deallocato.

Heap Questa regione di memoria non è gestita automaticamente dalla CPU e la sua dimensione è maggiore dello stack. Quando viene richiamata la nuova parola chiave, il compilatore inizia a cercare il primo blocco di memoria disponibile che si adatta alla dimensione della richiesta. e quando lo trova, viene contrassegnato come riservato utilizzando la funzione C incorporata malloc () e restituisce il puntatore a quella posizione. È anche possibile deallocare un blocco di memoria utilizzando la funzione C incorporata free (). Questo meccanismo causa la frammentazione della memoria e deve utilizzare i puntatori per accedere al blocco di memoria corretto, è più lento dello stack per eseguire le operazioni di lettura / scrittura.

Tipi personalizzati e incorporati Mentre C # fornisce un set standard di tipi incorporati che rappresentano numeri interi, booleani, caratteri di testo e così via, puoi usare costrutti come struct, class, interface ed enum per creare i tuoi tipi.

Un esempio di tipo personalizzato che utilizza il costrutto struct è:

struct Point
{
    public int X;
    public int Y;
};

Tipi di valore e riferimento Possiamo classificare il tipo C # nelle seguenti categorie:

  • Tipi di valore
  • Tipi di riferimento

Tipi di valore I tipi di valore derivano dalla classe System.ValueType e le variabili di questo tipo contengono i loro valori all'interno della loro allocazione di memoria nello stack. Le due categorie di tipi di valore sono struct ed enum.

L'esempio seguente mostra il membro del tipo boolean. Come puoi vedere non c'è un riferimento esplicito alla classe System.ValueType, questo accade perché questa classe è ereditata dalla struttura.

namespace System
{
    [ComVisible(true)]
    public struct Boolean : IComparable, IConvertible, IComparable<Boolean>, IEquatable<Boolean>
    {
        public static readonly string TrueString;
        public static readonly string FalseString;
        public static Boolean Parse(string value);
        ...
    }
}

Tipi di riferimento D'altra parte, i tipi di riferimento non contengono i dati effettivi memorizzati in una variabile, ma l'indirizzo di memoria dell'heap in cui è memorizzato il valore. Le categorie dei tipi di riferimento sono classi, delegati, array e interfacce.

In fase di esecuzione, quando viene dichiarata una variabile del tipo di riferimento, essa contiene il valore null fino a quando non viene assegnato un oggetto che è stato creato utilizzando le parole chiave new.

L'esempio seguente mostra i membri del tipo generico List.

namespace System.Collections.Generic
{
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(Generic.Mscorlib_CollectionDebugView<>))]
    [DefaultMember("Item")]
    public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
    {
        ...
        public T this[int index] { get; set; }
        public int Count { get; }
        public int Capacity { get; set; }
        public void Add(T item);
        public void AddRange(IEnumerable<T> collection);
        ...
    }
}

Nel caso in cui si desideri scoprire l'indirizzo di memoria di un oggetto specifico, la classe System.Runtime.InteropServices fornisce un modo per accedere agli oggetti gestiti dalla memoria non gestita. Nell'esempio seguente, utilizzeremo il metodo statico GCHandle.Alloc () per allocare un handle a una stringa e quindi il metodo AddrOfPinnedObject per recuperarne l'indirizzo.

string s1 = "Hello World";
GCHandle gch = GCHandle.Alloc(s1, GCHandleType.Pinned);
IntPtr pObj = gch.AddrOfPinnedObject();
Console.WriteLine($"Memory address:{pObj.ToString()}");

L'output sarà

Memory address:39723832

Riferimenti Documentazione ufficiale: https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2019


-1

Ci sono molti piccoli dettagli delle differenze tra tipi di valore e tipi di riferimento che sono dichiarati esplicitamente dallo standard e alcuni di essi non sono facili da capire, specialmente per i principianti.

Vedere lo standard ECMA 33, Common Language Infrastructure (CLI) . La CLI è anche standardizzata dall'ISO. Fornirei un riferimento, ma per ECMA dobbiamo scaricare un PDF e quel collegamento dipende dal numero di versione. Gli standard ISO costano denaro.

Una differenza è che i tipi di valore possono essere inscatolati, ma i tipi di riferimento generalmente non possono esserlo. Ci sono delle eccezioni ma sono piuttosto tecniche.

I tipi di valore non possono avere costruttori o finalizzatori di istanze senza parametri e non possono fare riferimento a se stessi. Fare riferimento a se stessi significa, ad esempio, che se esiste un tipo di valore Node, un membro di Node non può essere un Node . Penso che ci siano altri requisiti / limitazioni nelle specifiche, ma in tal caso non sono riuniti in un unico posto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.