Questa domanda è stata fatta prima qui Perché le structs e le classi separano i concetti in C #? , ma sono interessato a un aspetto specifico di questo aspetto a cui non è stata data risposta.
Sto cercando di capire perché i progettisti di linguaggio per C # e Swift (e forse altri) hanno optato per una progettazione linguistica che promuova implicitamente la definizione di un tipo come un tipo di valore (struct) rispetto a un tipo di riferimento (classe).
Se un linguaggio (come C ++) supporta solo la definizione di tipi composti in un modo, ad esempio con la parola chiave class, ma consente alle istanze di questo tipo di essere associate a una variabile per valore o per riferimento, ciò consente al consumatore del tipo di decidere se usare semantica del valore o semantica di riferimento.
Una caratteristica spesso citata del buon design del linguaggio è la sua capacità di trasmettere sinteticamente il maggior significato possibile in un modo locale. Eppure, questi linguaggi moderni sembrano nascondere la semantica del valore rispetto alla ref quando li usano. Esplorando questa asserzione con due esempi:
1) Chiamare un metodo:
Considera un metodo che opera su un array in C #. Questo array viene passato come argomento al metodo. Poiché gli array in C # sono tipi di riferimento, il metodo può scegliere di mutare l'array direttamente o creare un nuovo array mentre si opera su copie dei valori dell'array originale. Immaginiamo che in entrambi i casi, il metodo restituisca il riferimento a tale matrice al chiamante per supportare il concatenamento del metodo. Dato questo scenario, se il nome del metodo non fa riferimento a mutazioni o no, la strategia adottata non è facilmente conoscibile dall'API (cioè senza testare o leggere la documentazione).
Ovviamente nell'esempio sopra, l'implementatore del metodo in generale non dovrebbe modificare l'array originale senza una buona ragione. Ma il punto è che può farlo e il chiamante non può saperlo solo scrivendo la chiamata al metodo.
Viceversa, se il design del linguaggio assume semantica di valore ovunque (anche se potrebbe passare dei riferimenti per motivi di prestazioni sotto la copertura, questo è un problema di implementazione), l'esempio sopra non sarebbe possibile. Se l'implementatore del metodo desidera mutare la matrice originale, richiederebbe che il parametro sia esplicitamente contrassegnato come riferimento alla matrice, comunicando un strong suggerimento al chiamante che la matrice verrà modificata.
2) Variabili locali:
Considera una classe di persone di base con membri tipici come nome, data di nascita, ecc.
var p1 = new Person();
p1.name = "Tom";
var p2 = p1; //Note I would never do this but I'm trying to keep the example trivial for brevity.
p2.name = "Peter";
Poiché Person
è una classe, viene creata una sola Persona a cui fa riferimento sia p1
che p2
. Pertanto, in un esempio più complesso, questa mutazione indiretta di p1 potrebbe non essere intenzionale. Tuttavia, se Person
è stato dichiarato come struct, verrà creata una copia e p1
avrebbe mantenuto il nome di "Tom". Di nuovo, questo richiede all'utente del tipo di sapere se il tipo è stato definito come una struct o una classe - che le informazioni non sono prontamente disponibili nel contesto locale.
Se la lingua supporta solo la semantica del valore per impostazione predefinita con riferimenti espliciti in uso, questo tipo di errore non sarebbe possibile. Le cose sarebbero ancora più chiare se la lingua richiedesse un operatore diverso per accedere ai membri per refs vs. vals (ad esempio ->
operator / .
operator).
var p1 = new Person();
p1.name = "Tom";
var p2 = &p1;
p2->name = "Peter"; //It is pretty clear that we are using a reference here because the language forces a different operator.
Quindi, di nuovo, data la premessa di un buon linguaggio == linguaggio chiaro / non ambiguo, perché i progettisti di linguaggi moderni ritengono che sia meglio mettere semantica di tipo reference vs value nelle mani del type designer invece del tipo consumer?