tags:

views:

1675

answers:

8

What I have is a collection of classes that all implement the same interface but can be pretty wildly different under the hood. I want to have a config file control which of the classes go into the collection upon starting the program, taking something that looks like

<class1 prop1="foo" prop2="bar"/>

and turning that into

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

in a very generic way. The thing I don't know how to do is take the string "prop1" in the config file and turn that into the actual property accessor in the code. Are there any metaprogramming facilities in C# to allow that?

A: 

Reflection is what you want. Reflection + TypeConverter. Don't have much more time to explain, but just google those, and you should be well on your way. Or you could just use the xml serializer, but then you have to adhere to a format, but works great.

Darren Kopp
+4  A: 

It may be easier to serialise the classes to/from xml, you can then simply pass the XmlReader (which is reading your config file) to the deserializer and it will do the rest for you..

This is a pretty good article on serialization

Edit

One thing I would like to add, even though reflection is powerful, it requires you to know some stuff about the type, such as parameters etc.

Serializing to XML doesnt need any of that, and you can still have type safety by ensuring you write the fully qualified type name to the XML file, so the same type is automatically loaded.

Rob Cooper
+1  A: 

Plenty of metaprogramming facilities.

Specifically, you can get a reference to the assembly that holds these classes, then easily get the Type of a class from its name. See Assembly.GetType Method (String).

From there, you can instantiate the class using Activator or the constructor of the Type itself. See Activator.CreateInstance Method.

Once you have an instance, you can set properties by again using the Type object. See Type.GetProperty Method and/or Type.GetField Method along PropertyInfo.SetValue Method.

Frank Krueger
+3  A: 

Reflection or XML-serialization is what you're looking for.

Using reflection you could look up the type using something like this

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;
}

Note that this will go through all assemblies. You might want to reduce it to only include the currently executing assembly.

For assigning property values you also use reflection. Something along the lines of

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

While reflection might be the most flexible solution you should also take a look at XML-serialization in order to skip doing the heavy lifting yourself.

Markus Olsson
+4  A: 

Reflection allows you to do that. You also may want to look at XML Serialization.

Type type = blah.GetType();
PropertyInfo prop = type.GetProperty("prop1");
prop.SetValue(blah, "foo", null);
Lars Truijens
+1  A: 

You can do that with XAML.

Eric Haskins
No you can't. XAML is XML representation of "defined objects", not the other way around. XAML instantiates objects doesn't define them.
Pop Catalin
@ Pop Catalin I'm not sure what you mean. From their question I'm fairly sure they have several classes which implement an interface. They want to be able to create a collection of these classes, at run-time. That is what XAML is made to do.
Eric Haskins
+3  A: 

I would also suggest Xml serialization as others have already mentioned. Here is a sample I threw together to demonstrate. Attributes are used to connect the names from the Xml to the actual property names and types in the data structure. Attributes also list out all the allowed types that can go into the Things collection. Everything in this collection must have a common base class. You said you have a common interface already -- but you may have to change that to an abstract base class because this code sample did not immediately work when Thing was an interface.

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\"?>" + 
       "<config>" +
       "<stuff>" + 
       "  <class1 prop1=\"foo\" prop2=\"bar\"></class1>" +
       "  <class2 prop1=\"FOO\" prop2=\"BAR\" prop3=\"42\"></class2>" +
       "</stuff>" +
       "</config>";
      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<Thing> things;

     public ThingCollection()
     {
      Things = new List<Thing>();
     }

     [XmlArray("stuff")]
     [XmlArrayItem(typeof(SomeThing))]
     [XmlArrayItem(typeof(SomeThingElse))]
     public List<Thing> Things
     {
      get { return things; }
      set { things = value; }
     }
    }
}
Brian Ensink
A: 

I recently did something very similar, I used an abstract factory. In fact, you can see the basic concept here:

http://stackoverflow.com/questions/27294/abstract-factory-design-pattern

FlySwat