Click here to Skip to main content
15,122,043 members
Articles / Programming Languages / C#
Tip/Trick
Posted 28 Mar 2017

Tagged as

Stats

28.7K views
102 downloads
7 bookmarked

C# Initialize Class with Instance of Its Base

Rate me:
Please Sign up or sign in to vote.
2.97/5 (10 votes)
1 Apr 2017CPOL3 min read
How to automatically initialize a class for an inherited class instance

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:

C#
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:

C#
public class Apple : Fruit {
    public int NumberOfWorms { get; set; }
    
    public Apple(Fruit fruit, int noOfWorms) { 
        base = fruit; // <- nope! Not allowed!
        this.NumberOfWorms = noOfWorms;
    }
}

Now you can of course manually copy all properties like this:

C#
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.

C#
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) {
                // Skip unreadable and writeprotected ones
                if (!pi.CanRead || !pi.CanWrite) continue;
                // Read value
                object value = pi.GetValue(baseInstance, null);
                // Get Property of target class
                PropertyInfo pi_target = t.GetProperty(pi.Name);
                // Write value to target
                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:

C#
using System;
// Here comes the using line:
using BjSTools.Helpers;

namespace TestArea {
    public class Apple : Fruit {
        public int NumberOfWorms { get; set; }

        // New constructor
        public Apple(Fruit fruit, int noOfWorms) {
            // This copies all the properties from fruit to this instance
            this.FillProperties(fruit);
            // Now we can take care of the new properties
            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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Bjørn
Software Developer
Germany Germany
I'm working mainly on .NET Compact Framework C# on mobile devices at work. At home it's .NET Full Framework C# and a bit JavaScript.

Comments and Discussions

 
QuestionYour way is cool, but evil Pin
Maximys1-Apr-17 7:23
MemberMaximys1-Apr-17 7:23 
AnswerRe: Your way is cool, but evil Pin
Bjørn3-Apr-17 22:45
MemberBjørn3-Apr-17 22:45 
Hello Maximys,

as I'm just looking at the properties here (no fields) and their getters and setters are called as if you'd set them yourself.

Furthermore I explicitly use the target type in the FillProperties method to determine the PropertyInfo of the target class so overridden property's setters are called as well.

Just to make sure, I wrote a little test scenario:
\```C#
public class Test1 {
    public string Name { get; set; }
    public int[] Numbers  {
        get { return string.IsNullOrEmpty(this.NumbersText) ? 
                new int[0] : 
                this.NumbersText.Split('|').Select(n => int.Parse(n)).ToArray(); }
        set { this.NumbersText = string.Join("|", value.Select(n => n.ToString()).ToArray()); }
    }
    public virtual List<byte> Data { get; set; }
    public Guid Id { get; set; }
    public Guid? Id2 { get; set; }

    public string NumbersText;
}
public class Test2 : Test1 {
    private DateTime _lastDataSet = DateTime.MinValue;
    private List<byte> _data = new List<byte>();
    public override List<byte> Data {
        get { return _data; }
        set {
            _data = value;
            _lastDataSet = DateTime.Now;
        }
    }

    public Test2(Test1 bInst) {
        this.FillProperties(bInst);
    }
}

Test1 t1 = new Test1() {
    Name = "Testing",
    Data = new List<byte>() { 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0D },
    Numbers = new int[] { 1, 1, 2, 3, 5, 8, 13 },
    Id = Guid.NewGuid()
};

Test2 t2 = new Test2(t1);
```

I set the break points in the getters and setters and it does jump into them when calling the GetValue and SetValue methods of the PropertyInfo.
(/)
( . .)
c(")(") NeoBurn


modified 4-Apr-17 3:51am.

SuggestionThe OOP way Pin
Midi_Mick1-Apr-17 5:19
professionalMidi_Mick1-Apr-17 5:19 
GeneralRe: The OOP way Pin
Bjørn1-Apr-17 5:35
MemberBjørn1-Apr-17 5:35 
QuestionHas its Place Pin
RandyBuchholz30-Mar-17 18:38
MemberRandyBuchholz30-Mar-17 18:38 
PraiseThanks! Pin
Jim Meadors29-Mar-17 19:58
MemberJim Meadors29-Mar-17 19:58 
QuestionSorry, too many issues Pin
wkempf28-Mar-17 8:27
Memberwkempf28-Mar-17 8:27 
GeneralRe: Sorry, too many issues Pin
David Rush28-Mar-17 10:23
professionalDavid Rush28-Mar-17 10:23 
GeneralRe: Sorry, too many issues Pin
wkempf29-Mar-17 2:46
Memberwkempf29-Mar-17 2:46 
GeneralRe: Sorry, too many issues Pin
David Rush29-Mar-17 7:21
professionalDavid Rush29-Mar-17 7:21 
GeneralRe: Sorry, too many issues Pin
Bjørn30-Mar-17 1:54
MemberBjørn30-Mar-17 1:54 
AnswerRe: Sorry, too many issues Pin
Bernhard Hiller31-Mar-17 3:56
MemberBernhard Hiller31-Mar-17 3:56 
QuestionRe: Sorry, too many issues Pin
Bjørn1-Apr-17 5:08
MemberBjørn1-Apr-17 5:08 
AnswerRe: Sorry, too many issues Pin
Bernhard Hiller2-Apr-17 22:03
MemberBernhard Hiller2-Apr-17 22:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.