Introduction
Imagine an existing project which uses classes that simply represent database tables. There is no relation between the classes and you have to manually find and handle sub-classes including their reference tables.
I recently had such a project and quickly got a headache because I couldn't change the existing project but had to write a tool to crawl though some data of it. Instead of using SQL in every single part of the project to combine the structure, I wanted to have that in one place so I wrote wrapper classes.
And that's where my problems began...
Background
Casting in C# works in one direction only: You can case an instance of a class in any inherited class - no matter how nested it is - up to being an object
:
public class Fruit {
public float Sugar { get; set; }
public int Size { get; set; }
}
public class Apple : Fruit {
public int NumberOfWorms { get; set; }
}
Apple a = new Apple() { Sugar = 5.0f, Size = 10, NumberOfWorms = 0 }
TreeFruit tf = (TreeFruit)a;
Fruit f = (Fruit)a;
object o = (object)a;
Now the way to check this, is to ask if every instance of A is a B, then you can cast it. E.g. every Apple
is a Fruit
so you can cast an Apple
into a Fruit
. But not every Fruit
is an Apple
so you cannot cast a Fruit
in an Apple
.
At this point, it gets a bit nasty to explain, because a Fruit
can only have properties every Apple
will have too. So why is it not possible to just instantiate an Apple
with a Fruit
instance that fills all Fruit
properties of the apple
? I'm thinking of doing something like this - which of course doesn't work:
public class Apple : Fruit {
public int NumberOfWorms { get; set; }
public Apple(Fruit fruit, int noOfWorms) {
base = fruit;
this.NumberOfWorms = noOfWorms;
}
}
Now you can of course manually copy all properties like this:
public class Apple : Fruit {
public int NumberOfWorms { get; set; }
public Apple(Fruit fruit, int noOfWorms) {
this.Sugar = fruit.Sugar;
this.Size = fruit.Size;
this.NumberOfWorms = noOfWorms;
}
}
But this can quickly evolve into a whole load of work and regarding the maintenance of the code, this is a nightmare because every time the base class changes, the inheriting classes have to be adjusted too - and if you just add a property to the base class, it'll not even show up as a compiling error and produces hard to find bugs somewhere completely unrelated.
Using the Code
My solution to this is a helper method that can be called in the constructor of an inheriting class just to do the work by looking up the base's properties via Reflection and then copy them over to the inheriting instance.
using System;
using System.Reflection;
namespace BjSTools.Helpers {
public static class InheritHelper {
public static void FillProperties<T, Tbase>(this T target, Tbase baseInstance)
where T : Tbase {
Type t = typeof(T);
Type tb = typeof(Tbase);
PropertyInfo[] properties = tb.GetProperties();
foreach (PropertyInfo pi in properties) {
if (!pi.CanRead || !pi.CanWrite) continue;
object value = pi.GetValue(baseInstance, null);
PropertyInfo pi_target = t.GetProperty(pi.Name);
pi_target.SetValue(target, value, null);
}
}
}
}
It's an extension method so to use it, you just need to add a using
line and call the extension method on yourself:
using System;
using BjSTools.Helpers;
namespace TestArea {
public class Apple : Fruit {
public int NumberOfWorms { get; set; }
public Apple(Fruit fruit, int noOfWorms) {
this.FillProperties(fruit);
this.NumberOfWorms = noOfWorms;
}
}
}
And it works like a charm.
Points of Interest
I use Reflection here. There are many people out there rejecting the use of Reflection because it is indeed slower than manual mapping. You'll have to choose yourself rather your priority is speed or coding efford.
Note that I explicitly look for the PropertyInfo
of the inheriting type instead of using the base PropertyInfo
twice. This is because otherwise overrides would not work when calling the base property.
Also note that this method is explicitly limited to work on instances with their base type. You can delete the where
statement of the method definition to eliminate this limitation but be aware that you should add additional checks if the target properties are available and if their type is the same because without the limitation, you can copy any properties of any instance to any instance of a completely different class.
And yet another note: The method copies reference values by reference! So if a List<>
is copied, it's the same one in the target and the base instance and if you change one, the other is changed as well.