2011-01-05 12 views
27

Dopo il debug di un problema particolarmente complicato in VB.NET che coinvolge l'ordine in cui le variabili di istanza sono inizializzate, ho scoperto che c'è una discrepanza di rottura tra il comportamento che mi aspettavo da C# e il comportamento effettivo in VB.NET.È possibile forzare VB.NET a inizializzare le variabili di istanza PRIMA di richiamare il costruttore del tipo di base?

Nota bene: Tale questione riguarda una leggera discrepanza nei comportamenti di VB.NET e C#. Se sei un fanatico della lingua che non è in grado di fornire una risposta diversa da "ecco perché dovresti usare C#, noob", non c'è niente da vedere qui; gentilmente andare avanti

particolare, ho aspettato il comportamento delineata dalla C# Language Specification (enfasi aggiunta):

Quando un costruttore di istanza senza inizializzazione del costruttore, o ha un inizializzatore costruttore del modulo base(...), tale costruttore esegue implicitamente le inizializzazioni specificate dagli inizializzatori di variabile dei campi di istanza dichiarati nella sua classe. Corrisponde a una sequenza di assegnazioni che vengono eseguite immediatamente dopo l'immissione nel costruttore e prima dell'invocazione implicita del costruttore della classe base diretta. Gli inizializzatori variabili vengono eseguiti nell'ordine testuale in cui appaiono nella dichiarazione della classe.

Contrasto che, con la porzione di VB.NET Language Specification riguardante Instance Constructors, che dice (enfasi aggiunta):

Quando prima dichiarazione di un costruttore ha la forma MyBase.New(...), il costruttore esegue implicitamente la inizializzazioni specificate dagli inizializzatori variabili delle variabili di istanza dichiarate nel tipo. Corrisponde a una sequenza di assegnazioni che vengono eseguite immediatamente dopo aver richiamato il costruttore del tipo di base diretta. Tale ordinamento garantisce che tutte le variabili di istanza di base siano inizializzate dai rispettivi inizializzatori di variabili prima che vengano eseguite le istruzioni che hanno accesso all'istanza.

La discrepanza qui è immediatamente ovvia. C# inizializza le variabili a livello di classe prima del chiamando il costruttore di base. VB.NET fa esattamente il contrario, apparentemente preferendo chiamare il costruttore di base prima del impostando i valori dei campi di istanza.

Se si desidera vedere del codice, this related question fornisce un esempio più concreto del comportamento divergente. Sfortunatamente, non fornisce alcun suggerimento su come si possa costringere VB.NET a seguire il modello stabilito da C#.

Sono meno interessato al motivo per cui i progettisti delle due lingue hanno scelto approcci così divergenti rispetto a possibili soluzioni alternative per il problema. In definitiva, la mia domanda è la seguente: Esiste un modo per scrivere o strutturare il mio codice in VB.NET per forzare l'inizializzazione delle variabili di istanza prima del Il costruttore del tipo di base è chiamato, come lo è il comportamento standard in C# ?

+1

Non credo sia possibile modificare questo - e il compilatore VB si batterà contro qualsiasi tentativo di farlo. Se fai affidamento su un comportamento del genere, di solito è un'indicazione di problemi altrove nel tuo codice. È stato consigliato non chiamare i membri virtuali all'interno dei costruttori per un lungo periodo di tempo (come nel codice di esempio nell'altra domanda) –

+0

@Damien: hai ragione a proposito del fatto che è una cattiva pratica chiamare membri virtuali all'interno dei costruttori. Sfortunatamente, quella decisione non era mia. Sto sottoclassi un tipo fornito dal Framework, ignorando anche uno dei suoi metodi virtuali. Sono abbastanza sicuro che i problemi si trovano al di fuori del mio codice, e l'unica cosa che posso pensare di aggiustare questo "brutto scherzo" urla. –

+0

vedi anche http://stackoverflow.com/q/12585555/185593 – serhio

risposta

8

Se si hanno membri virtuali che verranno richiamati durante la costruzione (contro i migliori consigli, ma ne abbiamo già concordato), è necessario spostare l'inizializzazione in un metodo separato, che può proteggersi da più chiama (cioè se init è già successo, torna immediatamente). Tale metodo verrà quindi richiamato dai membri virtuali e dal costruttore, prima che si basino sull'inizializzazione che si è verificata.

È un po 'caotico, e può rappresentare una penalità minore per le prestazioni, ma c'è poco altro che puoi fare in VB.

2

Qualsiasi classe ben scritta deve garantire che qualsiasi membro virtuale che potrebbe essere invocato su un'istanza parzialmente costruita si comporti in modo ragionevole. C# prende la filosofia che un'istanza di classe i cui inizializzatori di campo siano stati eseguiti sarà in uno stato sufficientemente sensibile per consentire l'utilizzo di metodi virtuali. VB.net prende la filosofia che permette agli inizializzatori di campo di utilizzare un oggetto parzialmente costruito (e - con un po 'di lavoro - qualsiasi parametro passato al costruttore) è più utile di una garanzia che gli inizializzatori di campo funzioneranno prima di qualsiasi virtual i metodi sono chiamati.

IMHO, l'approccio corretto da un punto di vista del linguaggio design sarebbe stato quello di fornire un mezzo conveniente per indicare che gli inizializzatori di campo specificati dovrebbero essere eseguiti "in anticipo" o "in ritardo", poiché ci sono momenti in cui ognuno può essere utile (sebbene Preferisco lo stile "in ritardo", in quanto consente di mettere a disposizione parametri del costruttore per gli inizializzatori di campo). Per esempio:

 
Class ParamPasserBase(Of T) ' Generic class for passing one constructor parameter 
    Protected ConstructorParam1 As T 
    Sub New(Param As T) 
    ConstructorParam1 = Param 
    End SUb 
End Class 
Class MyThing 
    Inherits ParamPasserBase(Of Integer) 
    Dim MyArray(ConstructorParam1-1) As String 
    Sub New(ArraySize As Integer) 
    MyBase.New(ArraySize) 
    End Sub 
    ... 
End Class 

In C#, non c'è modo bello avere le dichiarazioni di campo o initializers fare uso di argomenti passati a un costruttore. In vb, può essere fatto in modo abbastanza pulito come illustrato sopra. Si noti che in VB, è anche possibile utilizzare un parametro del costruttore per riferimento per copiare una copia dell'oggetto in costruzione prima di eseguire qualsiasi inizializzatore di campo; se la routine Dispose di una classe è scritta correttamente per trattare oggetti parzialmente costruiti, un oggetto che getta nel suo costruttore può essere pulito in modo appropriato.

+1

Non sono sicuro di seguire quello che stai cercando di dire qui. Non ho la possibilità di ridisegnare entrambe le lingue e non sto discutendo particolarmente sul merito relativo delle scelte. Inoltre, da dove proviene 'RIAA()' nel tuo esempio di codice? Dici che puoi farlo ora in VB.NET. Non ho familiarità con la sintassi. È 'RIAA()' una classe wrapper personalizzata che hai scritto? E come è diverso da una frase 'using'? Oltre a ciò, qual è la differenza tra "vecchi" VB.NET e "nuovi" VB.NET? Uno è sicuro e l'altro no? Che cosa ha a che fare la distruzione con la domanda? –

+0

@Cody Gray: RIAA è un metodo di classe che registra un IDisposable per la pulizia automatica deterministica (nello stile di C++/RIAA) e quindi lo restituisce. In vb.net, gli inizializzatori di campo vengono eseguiti dopo la creazione dell'elenco di dispositivi monouso, pertanto il metodo RIAA può aggiungerli a tale elenco. VB.Net consente anche a un oggetto in costruzione di essere contrabbandato dal costruttore, quindi il costruttore può essere avvolto in un metodo factory che pulirà tutti gli oggetti registrati IDisposable se il costruttore lancia. – supercat

+0

Puoi darmi un link a dove viene discusso questo metodo RIAA? Non riesco a trovare la documentazione online.Non sono nemmeno sicuro di cosa abbiano a che fare gli elementi di disposizione con l'inizializzazione delle variabili di istanza prima che venga chiamato il costruttore della classe base. La mia domanda non riguarda nemmeno gli oggetti, tanto meno quelli che implementano 'IDisposable'. Il test case era in realtà un tipo 'Integer' o' Boolean', entrambi tipi di valore e non hanno bisogno di essere eliminati. RIAA è inutile qui in quanto è irrilevante. –

Problemi correlati