Perché il framework .NET non ha il concetto di classi come tipi di prima classe?


20

È noto a coloro che hanno familiarità con la storia che C # e il framework .NET iniziarono come essenzialmente "Delphi riscritto per sembrare Java", progettato dallo sviluppatore capo dietro Delphi, Anders Hejlsberg. Da allora le cose sono leggermente divergenti, ma all'inizio le somiglianze erano così evidenti che c'erano persino alcune serie speculazioni sul fatto che .NET fosse in realtà il prodotto di Borland.

Ma ultimamente ho esaminato alcune cose di .NET e una delle funzionalità più interessanti e utili di Delphi sembra mancare del tutto: il concetto di classe come tipo di dati di prima classe. Per coloro che non hanno familiarità con esso, il tipo TClassrappresenta un riferimento a una classe, simile al Typetipo in .NET. Ma dove .NET utilizza Typeper la riflessione, Delphi utilizza TClassuna parte incorporata molto importante del linguaggio. Permette vari modi di dire utili che semplicemente non possono esistere e che non possono esistere senza di essa, come variabili di sottotipo di classe e metodi di classe virtuali.

Ogni linguaggio OO ha metodi virtuali, in cui diverse classi implementano lo stesso concetto fondamentale di un metodo in modi diversi, e quindi il metodo giusto viene chiamato in fase di esecuzione in base al tipo effettivo dell'istanza dell'oggetto su cui è chiamato. Delphi estende questo concetto alle classi: se si dispone di un riferimento TClass definito come sottotipo di classe specifico (ovvero class of TMyClasssignifica che la variabile può accettare qualsiasi riferimento di classe che eredita TMyClass, ma non nulla al di fuori dell'erarchia) a cui sono associati metodi virtuali di ambito di classe possono essere richiamati senza un'istanza utilizzando il tipo effettivo della classe. L'applicazione di questo modello ai costruttori rende banale un'implementazione di Factory, ad esempio.

Non sembra esserci nulla di equivalente in .NET. Con quanto utili siano i riferimenti di classe (e specialmente i costruttori virtuali e altri metodi di classe virtuali!), Qualcuno ha mai detto qualcosa sul perché sono stati esclusi?

Esempi specifici

Deserializzazione delle forme

Delphi VCL salva i moduli in DFMformato, un DSL per la descrizione di una gerarchia di componenti. Quando il lettore di moduli analizza i dati di DFM, viene eseguito su oggetti descritti in questo modo:

object Name: ClassName
   property = value
   property = value
   ...
   object SubObjectName: ClassName
      ...
   end
end

La cosa interessante qui è la ClassNameparte. Ogni classe di componente registra la propria volta TClasscon il sistema di streaming del componente initialization(pensa che i costruttori statici, solo leggermente diversi, garantiscano che si verifichino immediatamente all'avvio.) Questo registra ogni classe in un hashmap string-> TClass con il nome della classe come chiave.

Ogni componente scende dal TComponent, che ha un costruttore virtuale che prende un singolo argomento, Owner: TComponent. Qualsiasi componente può sovrascrivere questo costruttore per fornire la propria inizializzazione. Quando il lettore DFM legge un nome di classe, cerca il nome nella suddetta hashmap e recupera il riferimento di classe corrispondente (o genera un'eccezione se non è presente), quindi chiama il costruttore virtuale TComponent su di esso, che è noto per essere buono perché la funzione di registrazione accetta un riferimento di classe necessario per discendere da TComponent e si finisce con un oggetto del tipo corretto.

In mancanza di ciò, l'equivalente di WinForms è ... beh ... un gran casino per dirla senza mezzi termini, che richiede a qualsiasi nuovo linguaggio .NET di reimplementare completamente la propria forma (de) serializzazione. Questo è un po 'scioccante quando ci pensi; poiché lo scopo di avere un CLR è consentire a più lingue di utilizzare la stessa infrastruttura di base, un sistema in stile DFM avrebbe perfettamente senso.

Estensibilità

Una classe di gestione delle immagini che ho scritto può essere fornita con un'origine dati (come un percorso ai file delle immagini) e quindi caricare automaticamente nuovi oggetti immagine se si tenta di recuperare un nome che non è nella raccolta ma è disponibile nell'origine dati. Ha una variabile di classe digitata come class ofclasse dell'immagine base, che rappresenta la classe di tutti i nuovi oggetti da creare. Viene fornito con un valore predefinito, ma ci sono alcuni punti, quando si creano nuove immagini con scopi speciali, che le immagini dovrebbero essere configurate in diversi modi. (Crearlo senza un canale alfa, recuperare metadati speciali da un file PNG per specificare la dimensione dello sprite, ecc.)

Questo potrebbe essere fatto scrivendo grandi quantità di codice di configurazione e passando opzioni speciali a tutti i metodi che potrebbero finire per creare un nuovo oggetto ... oppure potresti semplicemente creare una sottoclasse della classe di immagini di base che sovrascrive un metodo virtuale in cui l'aspetto in questione viene configurato, quindi utilizzare un blocco try / finally per sostituire temporaneamente la proprietà "classe predefinita" in base alle esigenze e quindi ripristinarla. Farlo con variabili di riferimento di classe è molto più semplice, e non è qualcosa che si potrebbe fare con i generici.



3
Puoi fornire uno o due esempi concreti in cui a TClassè utile, con un po 'di codice di esempio? Nella mia breve ricerca su Internet TClass, sto scoprendo che TClasspuò essere trasmesso come parametro. Questo viene fatto in .NET usando Generics. I metodi di fabbrica sono semplicemente contrassegnati staticin .NET e non richiedono l'esecuzione di un'istanza di classe.
Robert Harvey,

7
@RobertHarvey: Per me, personalmente, C # ha iniziato ad avere molto più senso una volta che ho smesso di guardarlo come ricaricato Java / C ++ e ho iniziato a trattarlo come Modula-2 con oggetti. È lo stesso con Java: tutti dicono che è stato influenzato dal C ++, ma non è vero. La sua influenza principale è Objective-C (e Smalltalk tramite quello), e Java avrà molto più senso una volta trattato come Smalltalk con tipi anziché C ++ con GC.
Jörg W Mittag,

3
@RobertHarvey: TClassè una funzionalità di linguaggio fondamentale che richiede il supporto del compilatore. Non è possibile "scrivere il proprio" senza scrivere la propria lingua, e anche per .NET ciò non sarebbe sufficiente perché il modello a oggetti è definito dal CLR, non da singole lingue. È qualcosa che deve letteralmente far parte del framework .NET stesso, o non può esistere.
Mason Wheeler,

4
Prima di tutto, posso dire con la massima certezza che .NET non era originariamente un prodotto "Borland". Come faccio a saperlo? Facevo parte (ancora sono) del core team originale che ha sviluppato Delphi. Ho lavorato a stretto contatto con Anders, Chuck, Gary e altri. Certo, sono certo che tu lo sappia. Per quanto riguarda l'esistenza di un riferimento di classe (come vengono chiamati TClass e costrutti simili) in .NET è stato probabilmente ritenuto non necessario a causa dell'esistenza di informazioni di tipo accessibili in fase di runtime. Delphi all'inizio aveva molte meno informazioni sul tipo e i riferimenti di classe erano un compromesso.
Allen Bauer,

Risposte:


5

.NET (CLR) era la terza generazione del Component Object Model (COM) di Microsoft, che agli inizi si riferiva al "runtime COM +". Microsoft Visual Basic e il mercato dei controlli COM / ActiveX hanno avuto molta più influenza sulle scelte specifiche di compatibilità architetturale CLR rispetto a Borland Delphi. (Certamente il fatto che Delphi avesse adottato i controlli ActiveX ha sicuramente contribuito a far crescere l'ecosistema ActiveX, ma COM / ActiveX esisteva prima di Delphi)

L'architettura di COM è stata elaborata in C (non in C ++) e focalizzata sulle interfacce, piuttosto che sulle classi. Inoltre, supportava la composizione degli oggetti, il che significa che un oggetto COM poteva in effetti essere costituito da diversi oggetti diversi, con l'interfaccia IUnknown che li collegava insieme. Ma IUnknown non ha avuto alcun ruolo nella creazione di oggetti COM, progettata per essere il più indipendente possibile dal linguaggio. La creazione di oggetti è stata generalmente gestita da IClassFactory e la riflessione da ITypeLibrary e le relative interfacce. Questa separazione delle preoccupazioni era indipendente dal linguaggio di implementazione e le caratteristiche di ciascuna interfaccia COM di base erano mantenute minime e ortogonali.

Quindi, a causa della popolarità dei controlli COM e ActiveX, l'architettura .NET è stata costruita per supportare COM IUnnown, IClassFactory e ITypeLibrary. In COM, queste interfacce non erano necessariamente sullo stesso oggetto, quindi raggrupparle insieme non aveva necessariamente senso.

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.