È 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 TClass
rappresenta un riferimento a una classe, simile al Type
tipo in .NET. Ma dove .NET utilizza Type
per la riflessione, Delphi utilizza TClass
una 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 TMyClass
significa 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 DFM
formato, 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 ClassName
parte. Ogni classe di componente registra la propria volta TClass
con 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 of
classe 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.
TClass
è utile, con un po 'di codice di esempio? Nella mia breve ricerca su Internet TClass
, sto scoprendo che TClass
può essere trasmesso come parametro. Questo viene fatto in .NET usando Generics. I metodi di fabbrica sono semplicemente contrassegnati static
in .NET e non richiedono l'esecuzione di un'istanza di classe.
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.