2011-01-28 15 views
7

Ok, ho scommesso su questa idea tutto il giorno ora, e ho raggiunto la parte in cui ammetto che non riesco a capire. È possibile che quello che sto facendo sia semplicemente stupido e che ci sia un modo migliore, ma è qui che mi ha portato il mio modo di pensare.Come si può trasmettere a un tipo usando il nome del tipo come stringa?

Sto tentando di usare un metodo generico per caricare moduli in WinForms:

protected void LoadForm<T>(ref T formToShow, bool autoLoaded) where T : FormWithWorker, new() 
{ 
    // Do some stuff 
} 

Le forme vengono caricati da un ToolStripMenuItem (sia attraverso la selezione della voce o tramite la voce di menu Apri Windows). Sono pigri, quindi ci sono campi per i moduli all'interno del genitore MDI, ma sono nulli finché non sono necessari. Ho un metodo comune utilizzato per ToolStripMenuItem_Click che gestisce tutti i clic delle voci di menu. Il metodo non ha un modo reale di sapere quale modulo viene chiamato, tranne per il fatto che il nome di ToolStripMenuItem corrisponde a un modello scelto per i nomi delle classi di moduli a cui corrispondono. Quindi, usando il nome di ToolStripMenuItem, posso indovinare il nome del tipo di modulo richiesto e il nome del campo privato assegnato per memorizzare il riferimento per quel modulo.

Utilizzando ciò, è possibile utilizzare un'istruzione switch crescente/di contratto con tipi hard-coded e corrispondenze di stringa per chiamare il metodo con il tipo specifico set (indesiderabile) oppure utilizzare Reflection per ottenere il campo e creare l'istanza del tipo Il problema per me è, System.Activator.CreateInstance fornisce un ObjectHandler che non può essere trasmesso ai tipi di cui ho bisogno. Ecco un frammento di ciò che ho finora:

string formName = "_form" + ((ToolStripMenuItem)sender).Name.Replace("ToolStripMenuItem", ""); 
string formType = formName.Substring(1); 

FieldInfo fi = this.GetType().GetField(formName, BindingFlags.NonPublic | BindingFlags.Instance); 

FormWithWorker formToLoad = (FormWithWorker)fi.GetValue(this); 
if (formToLoad == null) 
{ 
    formToLoad = (????)System.Activator.CreateInstance("MyAssemblyName", formType); 
} 

this.LoadForm(ref formToLoad, false); 
fi.SetValue(this, formToLoad); 

so il nome della stringa del tipo che va in per (????), ma al momento della compilazione non so il tipo perché cambia . Ho provato un sacco di modi per far funzionare questo cast/istanza, ma nessuno ha avuto successo. Mi piacerebbe molto sapere se è possibile eseguire un cast di questo tipo conoscendo il tipo solo come una stringa. Ho provato a usare Type.GetType(string, string) per eseguire il cast, ma al compilatore non piaceva. Se qualcuno ha un'idea diversa su come caricare dinamicamente i moduli perché sto semplicemente facendo stupidamente, per favore fatemelo sapere.

+0

rendere formToLoad oggetto e trasmetterlo a FormWithWorker nei punti LoadForm e SetValue? – asawyer

risposta

5

sareste meglio con la other overload che prende un Type e utilizzando per esempio Type.GetType(string).

FormWithWorker formToLoad = (FormWithWorker)fi.GetValue(this); 
if (formToLoad == null) 
{ 
    formToLoad = 
     (FormWithWorker)System.Activator.CreateInstance(Type.GetType("MyNamespace.MyFormType")); 
} 
+0

Ricordo che a un certo punto questo post menzionava specificamente l'uso del metodo '.UnWrap()' su 'System.Activator.CreateInstance'. Non lo dice ora, ma dovrebbe essere perché la soluzione al mio problema era dichiarare il campo privato con il tipo della classe base e poi chiamare 'CreateInstance (" namespace "," type "). Unwrap()'. Questo ha creato il tipo corretto e ha consentito al metodo di creare un'istanza appropriata. –

12

Questo problema viene in genere risolto mediante il casting su una classe base comune o un'interfaccia di tutti i tipi potenziali.

In C# 4, è anche possibile assegnarlo a una variabile dynamic per contenere il valore restituito e richiamare metodi arbitrari su di esso. I metodi saranno in ritardo. Tuttavia, preferisco attenermi alla prima soluzione ogni volta che è possibile.

+0

Voto in rialzo. Ho fatto qualcosa di molto simile usando un'interfaccia e funziona bene. – Chuck

+0

Ho provato questo metodo, ma il problema che ho riscontrato è che il tipo dinamico alla fine assegnato era la classe base e non la classe reale, quindi quando ha chiamato il metodo SetValue, ha lanciato un'eccezione di tipo casting. –

+0

@Joel come indicato dall'altra risposta, è necessario utilizzare il sovraccarico che restituisce l'oggetto stesso. Apparentemente, il sovraccarico che si sta utilizzando è utile per attivare oggetti remoti .NET. –

2

Secondo quello che hai, FormWithWorker deve essere (almeno) come classe di base del tipo che si sta istanziare, in modo da poter fare questo:

FormWithWorker formToLoad = (FormWithWorker)fi.GetValue(this); 
if (formToLoad == null) 
{ 
    formToLoad = (FormWithWorker)System.Activator.CreateInstance("MyAssemblyName", formType); 
} 
+0

Ho provato questo, ma ho ricevuto un errore di trasmissione a causa del metodo 'System.Activator.CreateInstance' che restituisce un ObjectHandler. Ho appena letto l'idea di @Andras Vass di chiamare 'Unwrap()' e combinare il 2 è ciò che cercherò in seguito. –

0

Mentre un'interfaccia comune è un modo per avvicinarsi a questo problema, le interfacce non sono pratiche per tutti gli sceneri. La decisione di cui sopra è uno di andare con un modello di fabbrica (switch statement - concrete class selection) o usare reflection. C'è uno stack post che affronta questo problema. Io credo che si possa applicare direttamente questo al vostro problema:

Method Factory - case vs. reflection

Problemi correlati