14

Suppose I've got a class named Foo.

I cannot change the Foo class but I wan't to extend it with a property named Bar of type string.

Also I've got a lot more classes like Foo so I'm interested in a 'generic' solution.

I'm looking into ExpandoObject, dynamic and it gives me the result I'm asking for but I was wondering it it could be done without using dynamic...

static void Main(string[] args)
{
    var foo = new Foo() { Thing = "this" };
    var fooplus = Merge(foo, new { Bar = " and that" });
    Console.Write(string.Concat(fooplus.Thing, fooplus.Bar));
    Console.ReadKey();
}

public class Foo
{
    public string Thing { get; set; }
}

public static dynamic Merge(object item1, object item2)
{
    if (item1 == null || item2 == null)
    return item1 ?? item2 ?? new ExpandoObject();

    dynamic expando = new ExpandoObject();
    var result = expando as IDictionary<string, object>;
    foreach (System.Reflection.PropertyInfo fi in item1.GetType().GetProperties())
    {
        result[fi.Name] = fi.GetValue(item1, null);
    }
    foreach (System.Reflection.PropertyInfo fi in item2.GetType().GetProperties())
    {
        result[fi.Name] = fi.GetValue(item2, null);
    }
    return result;
}
Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
Ralf de Kleine
  • 11,464
  • 5
  • 45
  • 87
  • 1
    Why not use an [Extension method](http://msdn.microsoft.com/en-us/library/vstudio/bb383977.aspx)? – John Alexiou Nov 01 '13 at 16:57
  • [partial classes](http://msdn.microsoft.com/en-us/library/wa80x488(v=vs.90).aspx)? – Liam Nov 01 '13 at 16:58
  • http://stackoverflow.com/a/619047/598289 You can't do that directly in C#, but here is a page claiming a workaround using reflection http://www.codeproject.com/Articles/386911/Csharp-Easy-Extension-Properties – SJuan76 Nov 01 '13 at 17:00
  • 3
    @Liam, I assume that "I cannot change the Foo class" also means he can't mark it as `partial`. – dcastro Nov 01 '13 at 17:00
  • If you can change the signature of the class to include `Partial`, that seems like a good way to do it, or simply `extend` it with a subclass. – Jeroen Vannevel Nov 01 '13 at 17:00
  • Why are you trying to do this? Adding properties to a class isn't something you can do in C#, and you could probably go about it in a better way than with `dynamic`. – Tim S. Nov 01 '13 at 17:00
  • An implementation of the Decorator pattern might come in handy here, but it would mean some refactoring of your code to use more abstraction. – trnelson Nov 01 '13 at 17:00
  • If your desired property is constant, you can use custom attribute. – Redwan Nov 01 '13 at 17:01
  • I'd use the adapter pattern here, and "inherit" the state/behaviour by using composition: http://www.oodesign.com/adapter-pattern.html – dcastro Nov 01 '13 at 17:01
  • 1
    If `Foo` isn't sealed, you could extend it, e.g. `public class FooBar : Foo { public string Bar { get; set; } }` – Tim S. Nov 01 '13 at 17:02
  • Guys, I've got a LOT of classes it would be great if I don't have to have a partial for each or extend it using inheritance. – Ralf de Kleine Nov 01 '13 at 17:04
  • 1
    Well, I suppose you'll have to use your current approach then. Beware of the performance implications though. – dcastro Nov 01 '13 at 17:07
  • you mention that you have a lot of classes, however, what you don't mention is the downstream maintenece requirement. you either invest in the foundations and take the pain now, or forever look over your shoulder for that angry support programmer in 6 months time -they know where you live!! :-) – jim tollan Nov 01 '13 at 17:08
  • ... so what i'm saying is, look at more traditional inheritance, rather than the blue period approach – jim tollan Nov 01 '13 at 17:09
  • final note - you could of course take an alternate approach and look into using Automapper and have a set of derived classes on your target, then simply Map in which ever direction suits your usecase – jim tollan Nov 01 '13 at 17:16
  • @jimtollan the downstream is JSON through WebAPI – Ralf de Kleine Nov 01 '13 at 17:43
  • @RalfdeKleine your classes have anything in common like and interface. This screams for an extension method on an interface. – John Alexiou Nov 01 '13 at 20:00
  • @ja72 Say I would have an interface could you show how to use an extension method for it? – Ralf de Kleine Nov 01 '13 at 23:09
  • @redwan How would a custom attribute help me? I need an extra property. – Ralf de Kleine Nov 01 '13 at 23:27

4 Answers4

17

Your problem can relatively easily be solved by using Reflection.Emit and run-time code generation.

Suppose now you have the following class that you would like to extend.

public class Person
{
    public int Age { get; set; }
}

This class represents a person, and contains a property named Age to represent the person's age.

In your case, you would also like to add a Name property of type string to represent the person's name.

The simplest and most streamlined solution would then be to define the following interface.

public interface IPerson
{   
    string Name { get; set; }
    int Age { get; set; }
}

This interface, which will be used to extend your class, should contain all the old properties your current class contains, and the new ones you would like to add. The reason for this will become clear in a moment.

You can now use the following class definition to actually extend your class by creating a new type at runtime which will also make it derive from the above mentioned interface.

class DynamicExtension<T>
{
    public K ExtendWith<K>()
    { 
        var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.Run);
        var module = assembly.DefineDynamicModule("Module");
        var type = module.DefineType("Class", TypeAttributes.Public, typeof(T));

        type.AddInterfaceImplementation(typeof(K));

        foreach (var v in typeof(K).GetProperties())
        {
            var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
            var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
            var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
            var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });

            var getGenerator = getter.GetILGenerator();
            var setGenerator = setter.GetILGenerator();

            getGenerator.Emit(OpCodes.Ldarg_0);
            getGenerator.Emit(OpCodes.Ldfld, field);
            getGenerator.Emit(OpCodes.Ret);

            setGenerator.Emit(OpCodes.Ldarg_0);
            setGenerator.Emit(OpCodes.Ldarg_1);
            setGenerator.Emit(OpCodes.Stfld, field);
            setGenerator.Emit(OpCodes.Ret);

            property.SetGetMethod(getter);
            property.SetSetMethod(setter);

            type.DefineMethodOverride(getter, v.GetGetMethod());
            type.DefineMethodOverride(setter, v.GetSetMethod());
        }

        return (K)Activator.CreateInstance(type.CreateType());
    }
}

To actually use this class, simply execute the following lines of code.

class Program
{
    static void Main(string[] args)
    {
        var extended = new DynamicExtension<Person>().ExtendWith<IPerson>();

        extended.Age = 25;
        extended.Name = "Billy";

        Console.WriteLine(extended.Name + " is " + extended.Age);

        Console.Read();
    }
}

You can now see that the reason we used an interface to extend our newly created class is so that we can have a type-safe way of accessing its properties. If we simply returned an object type, we would be forced to access its properties by Reflection.

EDIT

The following modified version is now able to instantiate complex types located inside the interface, and implement the other simple ones.

The definition of the Person class stays the same, while the IPerson interface now becomes the following.

public interface IPerson
{
    string Name { get; set; }

    Person Person { get; set; }
}

The DynamicExtension class definition now changes to the following.

class DynamicExtension<T>
{
    public T Extend()
    {
        var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Assembly"), AssemblyBuilderAccess.Run);
        var module = assembly.DefineDynamicModule("Module");
        var type = module.DefineType("Class", TypeAttributes.Public);

        type.AddInterfaceImplementation(typeof(T));

        foreach (var v in typeof(T).GetProperties())
        {
            var field = type.DefineField("_" + v.Name.ToLower(), v.PropertyType, FieldAttributes.Private);
            var property = type.DefineProperty(v.Name, PropertyAttributes.None, v.PropertyType, new Type[0]);
            var getter = type.DefineMethod("get_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, v.PropertyType, new Type[0]);
            var setter = type.DefineMethod("set_" + v.Name, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual, null, new Type[] { v.PropertyType });

            var getGenerator = getter.GetILGenerator();
            var setGenerator = setter.GetILGenerator();

            getGenerator.Emit(OpCodes.Ldarg_0);
            getGenerator.Emit(OpCodes.Ldfld, field);
            getGenerator.Emit(OpCodes.Ret);

            setGenerator.Emit(OpCodes.Ldarg_0);
            setGenerator.Emit(OpCodes.Ldarg_1);
            setGenerator.Emit(OpCodes.Stfld, field);
            setGenerator.Emit(OpCodes.Ret);

            property.SetGetMethod(getter);
            property.SetSetMethod(setter);

            type.DefineMethodOverride(getter, v.GetGetMethod());
            type.DefineMethodOverride(setter, v.GetSetMethod());
        }

        var instance = (T)Activator.CreateInstance(type.CreateType());

        foreach (var v in typeof(T).GetProperties().Where(x => x.PropertyType.GetConstructor(new Type[0]) != null))
        {
            instance.GetType()
                    .GetProperty(v.Name)
                    .SetValue(instance, Activator.CreateInstance(v.PropertyType), null);
        }

        return instance;
    }
}

We can now simply execute the following lines of code to get all the appropriate values.

class Program
{
    static void Main(string[] args)
    {
        var extended = new DynamicExtension<IPerson>().Extend();

        extended.Person.Age = 25;
        extended.Name = "Billy";

        Console.WriteLine(extended.Name + " is " + extended.Person.Age);

        Console.Read();
    }
}
Mario Stopfer
  • 541
  • 3
  • 8
  • I'm quite excited by this code, only downside is that I need a interface which matches Person and the extra property I need. But I might get it to work. Interesting. – Ralf de Kleine Nov 01 '13 at 23:40
  • 1
    +1 - whao- this looks very interesting and hints at some nice alternative uses. i like it very much. if you were able to refactor that to work in a similar way to the OP's merge (i.e. without the need to duplicate the base properties in the interface), that would be soo soo useful in lots of applications. might even have a bash at that refactor myself... – jim tollan Nov 02 '13 at 09:52
  • ahh - inote that the final result is iperson extended!! hmmm – jim tollan Nov 02 '13 at 09:57
  • @RalfdeKleine I can edit my answer to better fit your needs. What exactly do you want me to change? :D – Mario Stopfer Nov 02 '13 at 11:24
  • @jimtollan Exactly right. That is why I used the interface in the first place. So that we have a strongly typed result in the end, whose properties can be accessed in a type safe manner, and would be visible using intellisense. – Mario Stopfer Nov 02 '13 at 11:32
  • Mario - this keeps running thro my mind and something occurred to me. would it be possible to define the following: `public class Person { public int Age { get; set; } } public interface IPerson { string Name { get; set; } Person Person { get; set; } }` i.e. refactor your method to accept a full blown class object as a property, rather than copying the properties. then, you could refer to it in code as: `extended.Person.Age = 25;` . obviously, at present this doesn't work -but i'm sure it's tweakable to allow this.. go for it :) – jim tollan Nov 05 '13 at 22:45
  • ran out of space. obviously, the remaining issue to tackle in your `DynamicExtension` is the creation of a new class of type `'T'`, to cater for the class being passed at type level, rather than individual property level. this could of course be created when setting the properties -i.e. `extended.Person = new Person();` but that's a bit lame and not particularly logical. throwing this challenge out to anyone that reads this! – jim tollan Nov 05 '13 at 22:56
  • Jim, I have updated my answer to match what you had in mind. Could you just elaborate a bit more on how you want me to handle the Extend method, and how you think it should accept objects instead of types? – Mario Stopfer Nov 07 '13 at 15:36
  • hey mario -good job. ok, in the *perfect* world, i'd love to see your code SOMEHOW (don't ask me how), take the `Person` type that is part of `IPerson` and then when it is *extended*, return a brand new mixed type where the `Person` properties were *flattened* down onto the same level as the new `IPerson` properties. Basically, you'd now have a single new object with merged properties. I do btw understand the complxities of trying to get this to happen in design time with intellisense! but this would be a wonderful piece of *engineering* – jim tollan Nov 07 '13 at 15:51
  • but just to add, this is an excellent advance on your initial answer, it wraps everything up really nicely – jim tollan Nov 07 '13 at 15:56
  • in effect, the most PERFECT final version would be instantiated as such `var extended = new DynamicExtension().ExtendWith();`. In our `IPerson` interface, we'd ONLY have the new properties (no mention of `Person`). This call would then merge the properties found in `Person` and `IPerson` as a single type. This would mean that you could apply `IPerson` to a variety of other types i.e. `var extended = new DynamicExtension().ExtendWith();` etc... as i said, i understand how *almost* impossible this is!! :-) – jim tollan Nov 07 '13 at 16:09
  • 1
    Jim, it would certainly be possible for me to take both types and merge them into a single new one. But the resulting type that would be returned would be of type Object, therefore no intellisense. In order for us to use intellisense, we simply need to have the type, or its interface upfront. – Mario Stopfer Nov 07 '13 at 17:21
  • mario - yeah, this is too true. i think your update implementation is the closest to nailing the OP's requirement. I for one don't have a usecase for this yet, but it certainly caught my imagination. thanks for all the input -wish i could vote more than once!! :-) all the best for now.. – jim tollan Nov 07 '13 at 17:26
3

as my comments were getting very verbose, I thought I'd add a new answer. this answer is completely Mario's work and thinking, only has my minor addition to exemplify what I'm trying to put across.

There are a few minor changes to mario's example that would make this work very well, namely, just changing the fact that the existing properties are added as the class object, rather than duplicating the entire class. Anyway, here's how this looks (only amended sections added, all else remains as per mario's answer):

public class Person
{
    public int Age { get; set; }
    public string FaveQuotation { get; set; }
}

for the IPerson interface, we add the actual Person class, rather than copying the properties:

public interface IPerson
{
    // extended property(s)
    string Name { get; set; }
    // base class to extend - tho we should try to overcome using this
    Person Person { get; set; }
}

This translates to an updated usage of:

static void Main(string[] args)
{
    var extended = new DynamicExtension<Person>().ExtendWith<IPerson>();

    var pocoPerson = new Person
    {
        Age = 25,
        FaveQuotation = "2B or not 2B, that is the pencil"
    };

    // the end game would be to be able to say: 
    // extended.Age = 25; extended.FaveQuotation = "etc";
    // rather than using the Person object along the lines below
    extended.Person = pocoPerson;
    extended.Name = "Billy";

    Console.WriteLine(extended.Name + " is " + extended.Person.Age 
        + " loves to say: '" + extended.Person.FaveQuotation + "'");

    Console.ReadKey();
}

Hope this helps the original OP, I know it made me think, tho the jury is still out as to whether the Person class should be flattened to the same level in the method as the new properties!! So in effect, using the line new DynamicExtension<Person>().ExtendWith<IPerson>(); SHOULD return a fully extended new object -intellisence included. Tough call - hmmm...

jim tollan
  • 22,305
  • 4
  • 49
  • 63
2

Without having access to the class definition, the best you could do is create a class which is derived from the target class. Unless the original is Sealed.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
0

I know this is coming late. A nuget package that abstracts all the complexity required to extend a type at runtime has been created. It is as simple as:

var className = "ClassA";
var baseType = typeof(List<string>);
var typeExtender = new TypeExtender(className, baseType);
typeExtender.AddProperty("IsAdded", typeof(bool));
typeExtender.AddProperty<double>("Length");
var returnedClass = typeExtender.FetchType();
var obj = Activator.CreateInstance(returnedClass);

You can find more usage instructions on the repo TypeExtender. Nuget package is at nuget

Ndubuisi Jr
  • 461
  • 6
  • 13