C #: classi istanziate da XML

Quello che ho è una collezione di classi che implementano tutte la stessa interfaccia ma possono essere abbastanza selvaggiamente diverse sotto il cofano. Voglio avere un file di configurazione per controllare quali classi entrano nella collezione all’avvio del programma, prendendo qualcosa che assomiglia a:

 

e trasformandolo in:

 blah = new class1(); blah.prop1="foo"; blah.prop2="bar"; 

In un modo molto generico. La cosa che non so come fare è prendere la stringa prop1 nel file di configurazione e trasformarla nell’accessoria proprietà effettiva nel codice. Ci sono delle strutture di meta-programmazione in C # per permetterlo?

La riflessione ti consente di farlo. Si potrebbe anche voler vedere la serializzazione XML .

 Type type = blah.GetType(); PropertyInfo prop = type.GetProperty("prop1"); prop.SetValue(blah, "foo", null); 

Potrebbe essere più semplice serializzare le classi da / a xml, puoi semplicemente passare XmlReader (che sta leggendo il tuo file di configurazione) al deserializzatore e farà il resto per te ..

Questo è un buon articolo sulla serializzazione

modificare

Una cosa che vorrei aggiungere, anche se la riflessione è potente, richiede di conoscere alcune cose sul tipo, come i parametri, ecc.

La serializzazione in XML non ha bisogno di nulla di tutto ciò, e si può ancora avere la sicurezza del tipo assicurandosi di scrivere il nome del tipo completo nel file XML, in modo che lo stesso tipo venga caricato automaticamente.

Suggerirei anche la serializzazione Xml come altri hanno già menzionato. Ecco un campione che ho buttato insieme per dimostrare. Gli attributi vengono utilizzati per connettere i nomi dall’XML ai nomi e ai tipi di proprietà effettivi nella struttura dati. Gli attributi elencano anche tutti i tipi consentiti che possono entrare nella collezione Things . Tutto in questa raccolta deve avere una class base comune. Hai detto che hai già un’interfaccia comune, ma potresti doverlo cambiare in una class base astratta perché questo esempio di codice non funzionava immediatamente quando Thing era un’interfaccia.

 using System; using System.Collections.Generic; using System.Text; using System.Xml.Serialization; using System.IO; namespace ConsoleApplication1 { class Program { static void Main() { string xml = "< ?xml version=\"1.0\"?>" + "" + "" + " " + " " + "" + ""; StringReader sr = new StringReader(xml); XmlSerializer xs = new XmlSerializer(typeof(ThingCollection)); ThingCollection tc = (ThingCollection)xs.Deserialize(sr); foreach (Thing t in tc.Things) { Console.WriteLine(t.ToString()); } } } public abstract class Thing { } [XmlType(TypeName="class1")] public class SomeThing : Thing { private string pn1; private string pn2; public SomeThing() { } [XmlAttribute("prop1")] public string PropertyNumber1 { get { return pn1; } set { pn1 = value; } } [XmlAttribute("prop2")] public string AnotherProperty { get { return pn2; } set { pn2 = value; } } } [XmlType(TypeName="class2")] public class SomeThingElse : SomeThing { private int answer; public SomeThingElse() { } [XmlAttribute("prop3")] public int TheAnswer { get { return answer; } set { answer =value; } } } [XmlType(TypeName = "config")] public class ThingCollection { private List things; public ThingCollection() { Things = new List(); } [XmlArray("stuff")] [XmlArrayItem(typeof(SomeThing))] [XmlArrayItem(typeof(SomeThingElse))] public List Things { get { return things; } set { things = value; } } } } 

Riflessione o serializzazione XML è ciò che stai cercando.

Usando il riflesso puoi cercare il tipo usando qualcosa di simile

 public IYourInterface GetClass(string className) { foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (Type type in asm.GetTypes()) { if (type.Name == className) return Activator.CreateInstance(type) as IYourInterface; } } return null; } 

Si noti che questo passerà attraverso tutti gli assembly. Potresti volerlo ridurre per includere solo l’assembly attualmente in esecuzione.

Per l’assegnazione dei valori delle proprietà si utilizza anche la riflessione. Qualcosa sulla falsariga di

 IYourInterface o = GetClass("class1"); o.GetType().GetProperty("prop1").SetValue(o, "foo", null); 

Mentre la riflessione potrebbe essere la soluzione più flessibile, dovresti anche dare un’occhiata alla serializzazione XML per evitare di eseguire autonomamente il sollevamento pesi.

Un sacco di servizi di metaprogrammazione.

Nello specifico, puoi ottenere un riferimento all’assembly che contiene queste classi, quindi ottenere facilmente il Type di una class dal suo nome. Vedi Metodo Assembly.GetType (stringa) .

Da lì, puoi istanziare la class usando Activator o il costruttore del Type stesso. Vedi il metodo Activator.CreateInstance .

Una volta che hai un’istanza, puoi impostare le proprietà nuovamente usando l’object Type . Vedere Metodo Type.GetProperty e / o Type.GetField sul metodo PropertyInfo.SetValue .

Di recente ho fatto qualcosa di molto simile, ho usato una fabbrica astratta. In effetti, puoi vedere il concetto di base qui:

Modello astratto di progettazione di fabbrica

Penso che tu possa utilizzare Dynamics qui. Crea ExpandoObject, può essere usato come dizionario per l’impostazione delle proprietà da xml config.

La riflessione è ciò che vuoi. Reflection + TypeConverter. Non ho molto più tempo per spiegare, ma solo quelli di Google, e dovresti essere sulla buona strada. Oppure potresti semplicemente usare il serializzatore xml, ma poi devi rispettare un formato, ma funziona alla grande.