Click here to Skip to main content
13,152,321 members (40,512 online)
Click here to Skip to main content
Add your own
alternative version

Stats

7.1K views
87 downloads
5 bookmarked
Posted 28 Mar 2017

C# Initialize Class with Instance of Its Base

, 1 Apr 2017
Rate this:
Please Sign up or sign in to vote.
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:

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; // <- nope! Not allowed!
        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) {
                // 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:

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.

You may also be interested in...

Comments and Discussions

 
QuestionYour way is cool, but evil Pin
Maximys1-Apr-17 6:23
memberMaximys1-Apr-17 6:23 
AnswerRe: Your way is cool, but evil Pin
Bjørn3-Apr-17 21:45
memberBjørn3-Apr-17 21:45 
SuggestionThe OOP way Pin
Midi_Mick1-Apr-17 4:19
professionalMidi_Mick1-Apr-17 4:19 
GeneralRe: The OOP way Pin
Bjørn1-Apr-17 4:35
memberBjørn1-Apr-17 4:35 
QuestionHas its Place Pin
RandyBuchholz30-Mar-17 17:38
memberRandyBuchholz30-Mar-17 17:38 
PraiseThanks! Pin
Jim Meadors29-Mar-17 18:58
memberJim Meadors29-Mar-17 18:58 
QuestionSorry, too many issues Pin
wkempf28-Mar-17 7:27
memberwkempf28-Mar-17 7:27 
GeneralRe: Sorry, too many issues Pin
David Rush28-Mar-17 9:23
memberDavid Rush28-Mar-17 9:23 
GeneralRe: Sorry, too many issues Pin
wkempf29-Mar-17 1:46
memberwkempf29-Mar-17 1:46 
GeneralRe: Sorry, too many issues Pin
David Rush29-Mar-17 6:21
memberDavid Rush29-Mar-17 6:21 
GeneralRe: Sorry, too many issues Pin
Bjørn30-Mar-17 0:54
memberBjørn30-Mar-17 0:54 
AnswerRe: Sorry, too many issues Pin
Bernhard Hiller31-Mar-17 2:56
professionalBernhard Hiller31-Mar-17 2:56 
QuestionRe: Sorry, too many issues Pin
Bjørn1-Apr-17 4:08
memberBjørn1-Apr-17 4:08 
AnswerRe: Sorry, too many issues Pin
Bernhard Hiller2-Apr-17 21:03
professionalBernhard Hiller2-Apr-17 21: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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170924.2 | Last Updated 1 Apr 2017
Article Copyright 2017 by Bjørn
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid