C Hogg
C Hogg

Reputation: 1001

C#: instantiating classes from XML

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 meta-programming facilities in C# to allow that?

Upvotes: 13

Views: 5029

Answers (8)

Dima
Dima

Reputation: 6741

I think you can utilize Dynamics here. Create ExpandoObject, it can be used either as Dictionary for setting properties from xml config.

Upvotes: 1

Markus Olsson
Markus Olsson

Reputation: 22580

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.

Upvotes: 6

FlySwat
FlySwat

Reputation: 175593

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

Abstract Factory Design Pattern

Upvotes: 1

Brian Ensink
Brian Ensink

Reputation: 11218

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

Upvotes: 8

Rob Cooper
Rob Cooper

Reputation: 28867

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.

Upvotes: 9

Lars Truijens
Lars Truijens

Reputation: 43595

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

Upvotes: 9

Frank Krueger
Frank Krueger

Reputation: 70983

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.

Upvotes: 3

Darren Kopp
Darren Kopp

Reputation: 77627

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.

Upvotes: 0

Related Questions