Click here to Skip to main content
Click here to Skip to main content

Convert Base Type to Derived Type

, 12 Mar 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
.NET method to convert an object into a derived type

Introduction

Casting and converting objects from type to type is an integral part of object oriented programming. When dealing with types in an inheritance chain, objects can be easily cast into a higher level. However, casting an object down into a derived type is not so easy. In many cases, it's not possible at all; however, there are specific circumstances where you may have this requirement.

Background

Recently, I had the need to convert an object into a descending type. The base type is a simple data object, with public properties representing values of private fields. The derived type has a few additional properties added on. I wanted to use the derived type as the data source for a grid, but my data manager only gave me the base type. My goal was to convert the base type into the derived type, then just fill in the missing properties.

I found that there really was no easy way to do this, without resorting to either converting the values one at a time, or using alternate methods such as reflection or serialization.

Suppose I have the following situation:

class Foo { 
    public int Prop1 { get; set; } 
    public int Prop2 { get; set; } 
} 
class Bar : Foo { 
    public int Prop3 { get; set; } 
}

static IEnumerable<Bar> ConvertToBar(List<Foo> foos) {
    // What do I do?
}

The most basic way of accomplishing this is to assign the values one at a time - not bad in our situation, but if I have a bunch of properties in the base, it can get kind of long.

static IEnumerable<Bar> ConvertToBar(IEnumerable<Foo> foos) {
    foreach (Foo foo in foos) {
        yield return new Bar() { Prop1 = foo.Prop1, Prop2 = foo.Prop2 };
    }
}

Doing this every once in a while is no big deal, but if you have to do it a lot, it can add an unnecessary level of complexity to your code, while opening up more room for error. I decided to write a method that will accomplish this without the need for writing your own conversion method.

Using the Code

Converting into a derived class using this technique is only possible in specific circumstances. The class must include properties that have no side effects, and the order of setting these values must not matter. The derived class must contain a parameterless constructor, and there should be no indexer properties. This may seem like a lot of restrictions, but since this technique is designed to work with simple data objects, it's not all that unreasonable.

I built a generic method named ToDerived that accepts an object of one type, and converts to one if its descended classes.

public static TDerived ToDerived<TBase, TDerived>(TBase tBase)
    where TDerived : TBase, new()
{
    TDerived tDerived = new TDerived();
    foreach (PropertyInfo propBase in typeof(TBase).GetProperties())
    {
        PropertyInfo propDerived = typeof(TDerived).GetProperty(propBase.Name);
        propDerived.SetValue(tDerived, propBase.GetValue(tBase, null), null);
    }
    return tDerived;
}

This is pretty straightforward reflection - I'm retrieving the properties from the base object, then assigning those values to the derived object. When this method is complete, all of the values from the base class will be assigned to, but the ones in only the derived class will still be their default value. In our Foo/Bar example, Prop1 and Prop2 will be transferred, but Prop3 will still be zero (default for int).

Of course, there are more things to think about. Properties can be public, internal, protected, or private. By default, the GetProperties() method only returns public properties. If we want to access other accessibilities, we can accomplish this using the BindingFlags enumeration. Adding a bindingFlags parameter to the method, we give the caller the option of setting exactly what she/he wants to do:

public static TDerived ToDerived<TBase, TDerived>(TBase tBase, BindingFlags bindingFlags)
    where TDerived : TBase, new()
{
    bool allowNonPublic = ((bindingFlags & BindingFlags.NonPublic) == 
					BindingFlags.NonPublic);
    TDerived tDerived = new TDerived();

    foreach (PropertyInfo propBase in typeof(TBase).GetProperties(bindingFlags))
    {
        PropertyInfo propDerived = typeof(TDerived).GetProperty
				(propBase.Name, bindingFlags);
        propDerived.SetValue(tDerived, propBase.GetValue(tBase, null), null);
    }
    return tDerived;
}

BindingFlags is a little tricky at first - it's a flag enumeration, and you need to define which properties you can find. Here are the two types that I'd be likely to use for this technique:

Bar bar = ToDerived<Foo, Bar>(foo, BindingFlags.Public | BindingFlags.NonPublic | 
    BindingFlags.Instance); /* gets all public, protected, and internal properties */
bar = ToDerived<Foo, Bar>(foo, BindingFlags.Public | 
	BindingFlags.Instance); /* public only */

Add just a couple more things - pass in an array or other collection of strings representing properties to ignore, mix in some LINQ to objects, exclude index parameters, put it all together, and here's the final product:

public static TDerived ToDerived<TBase, TDerived>
    (TBase tBase, BindingFlags bindingFlags, ICollection<string> propertiesToIgnore)
    where TDerived : TBase, new()
{
    if (tBase == null)
    {
        throw new ArgumentNullException("tBase");
    }

    bool allowNonPublic = ((bindingFlags & BindingFlags.NonPublic) == 
						BindingFlags.NonPublic);

    TDerived tDerived = new TDerived();

    var baseProperties =
        from propBase in typeof(TBase).GetProperties(bindingFlags)
        where ((propertiesToIgnore == null) || 
		(!propertiesToIgnore.Contains(propBase.Name)))
        where (propBase.GetGetMethod(allowNonPublic) != null)
        select propBase;

    foreach (PropertyInfo propBase in baseProperties)
    {
        PropertyInfo propDerived = typeof(TDerived).GetProperty
	(propBase.Name, bindingFlags, null, propBase.PropertyType, new Type[0], null);

        // Only set the value on a non-indexed property. 
        // An indexed property is more like a method than a property,
        // so there is no way to assign each of the values from the base 
        // to the derived type.
        if (
            (propDerived != null)
            && (propDerived.GetSetMethod(allowNonPublic) != null)
            && (propBase.GetIndexParameters().Length == 0)
            && (propDerived.GetIndexParameters().Length == 0)
            )
        {
            propDerived.SetValue(tDerived, propBase.GetValue(tBase, null), null);
        }
    }
    return tDerived;
}

All that's left is a few overloads:

public static TDerived ToDerived<tbase>(TBase tBase)
    where TDerived : TBase, new()
{
    BindingFlags bindingFlags = BindingFlags.Public | 
		BindingFlags.NonPublic | BindingFlags.Instance;
    return ToDerived<tbase>(tBase, bindingFlags);
}
public static TDerived ToDerived<tbase>
	(TBase tBase, ICollection<string> propertiesToIgnore)
    where TDerived : TBase, new()
{
    BindingFlags bindingFlags = BindingFlags.Public | 
		BindingFlags.NonPublic | BindingFlags.Instance;
    return ToDerived<tbase>(tBase, bindingFlags, propertiesToIgnore);
}
public static TDerived ToDerived<tbase>(TBase tBase, BindingFlags bindingFlags)
    where TDerived : TBase, new()
{
    return ToDerived<tbase>(tBase, bindingFlags, null);
}

Conclusion

Reflection isn't the most fun thing to do on the fly, but when using reusable generic code such as this, it's reasonably safe and performs pretty well. There is a very limited scope of use for this technique - objects are not generally designed to be converted to a child type, and there is no way to enforce the logical rules that apply here. However, there may be some situation out there where this could be useful.

The attached ZIP file contains a Visual Studio 2008 solution, with projects designed to compile under the .NET 3.5 Framework. It contains a JTConvert project, which contains the applicable codefile, and a JTConvert.TestFixture project, which contains various Unit Tests designed to run in NUnit.

History

  • 13th March, 2009: Initial post

License

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

Share

About the Author

Joe Enos
Software Developer Desert Schools Federal Credit Union
United States United States
Joe Enos is a software engineer in Phoenix, Arizona, with 8 years of .NET experience. Currently working as a software developer on a medium-sized team in Phoenix.
Follow on   Twitter

Comments and Discussions

 
GeneralInvolved classes inheritance isn't required PinmemberOleg V. Polikarpotchkin17-Mar-09 20:46 
GeneralRe: Involved classes inheritance isn't required PinmemberJoe Enos17-Mar-09 20:57 
GeneralYou could use Expression Trees instead of Reflection PinmemberOmer Mor13-Mar-09 11:21 
GeneralRe: You could use Expression Trees instead of Reflection PinmemberJoe Enos13-Mar-09 11:46 
AnswerRe: You could use Expression Trees instead of Reflection PinmemberOmer Mor14-Mar-09 4:42 
GeneralRe: You could use Expression Trees instead of Reflection PinmemberJoe Enos14-Mar-09 8:22 
GeneralFields PinmemberJoe Enos13-Mar-09 5:47 
QuestionScenarios for use PinsitebuilderNishant Sivakumar13-Mar-09 4:03 
AnswerRe: Scenarios for use PinmemberJoe Enos13-Mar-09 5:41 
GeneralRe: Scenarios for use Pinmembersupercat913-Mar-09 8:20 
GeneralRe: Scenarios for use PinmemberJoe Enos13-Mar-09 10:24 
GeneralRe: Scenarios for use Pinmembersupercat913-Mar-09 11:31 
GeneralRe: Scenarios for use PinmemberJoe Enos13-Mar-09 12:02 
QuestionRe: Scenarios for use Pinmemberstixoffire23-Jan-14 8:30 
GeneralRead-Only Properties, Fields, Example Code PinmemberChristoph Menge13-Mar-09 3:05 
GeneralRe: Read-Only Properties, Fields, Example Code PinmemberJoe Enos13-Mar-09 5:35 
GeneralFields perhaps PinmemberMojoflys13-Mar-09 2:43 
GeneralRe: Fields perhaps PinmemberJoe Enos13-Mar-09 5:29 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 13 Mar 2009
Article Copyright 2009 by Joe Enos
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid