Roka545
Roka545

Reputation: 3626

Convert List of Base Class to a List of Derived Class - possible?

I coworker of mine told me that it was possible to convert a List<BaseClass> to a List<DerivedClass> (they didn't show me a working example). I didn't think it was possible to convert a parent class into a child class (I do know however that it is possible to do it the other way around). I've seen several related questions with people saying it can't be done and people saying it can - none of the proposed solutions have worked for me at all. I always get a:

System.InvalidCastException.
Unable to cast object of type 'BaseClass' to 'ChildClass'

Is this absolutely possible? If so, what am I doing wrong? Here is my code (simplified for the purposes of this question):

Parent class:

public class ParentClass
{
    public string Name = "";
}

Child class:

public class ChildClass: ParentClass
{
    public string Date = "";
}

Converting:

List<ParentClass> parentList = ServiceApi.GetParentClassList().ToList();
List<ChildClass> childList = parentList.Cast<ChildClass>().ToList();

My actual code is significantly more complex but the same principles should apply.

Upvotes: 11

Views: 19639

Answers (5)

Joel Coehoorn
Joel Coehoorn

Reputation: 415735

I coworker of mine told me that it was possible to convert a List<BaseClass> to a List<DerivedClass> (they didn't show me a working example). I didn't think it was possible to convert a parent class into a child class

In a sense, you're both right.

Let's say you have types Animal, Cat (inherits Animal) and Dog (inherits Animal). A List<Animal> can contain both Cats and Dogs, but a List<Dog> cannot contain Cat objects.

If you have a List<Animal> that contains both Cat and Dog objects, nothing you do will ever let you produce a List<Dog> with everything from the original list. In this sense you are right that you cannot produce List<DerivedClass> from List<ParentClass>.

But there are times when you, as the programmer, have information about how an object is used that is not available to the compiler. If (and only if) you can know that a particular List<Animal> instance only contains Dog objects, you can always write something like this:

List<Dog> dogList = myAnimalListWithOnlyDogs.Cast<Dog>().ToList();

Again: this only works when you can guarantee every object in your list instance is convertible to a Dog object. If a Cat object somehow works it's way into your list, you'll end up with an exception at run time. Nevertheless, code like this is not uncommon.

In this sense, the coworker is right. When it comes to real working code, people do it anyway with great effect, and get away with it just fine, though a purist might argue this is a code smell indicating a need to use composition instead of inheritance.

Additionally, there may be times when a List<Animal> instance contains both Cat and Dog objects, and you only care about the Dog objects. In that case, you can do this:

List<Dog> dogList = myAnimalList.OfType<Dog>().ToList();

I've seen this pattern used often, especially when working with sets of WinForms controls.

In both examples above you are working with a new sequence. Adding or removing objects to the new sequence will not change the original. In this sense, you have not converted the original as much a created a replacement. However, the sequence contains the same objects, so editing a property on a Dog object in the new sequence will change that object in the original, because it's the same object instance.

Upvotes: 29

clamchoda
clamchoda

Reputation: 4951

they didn't show me a working example.

Going from base class to a derived class is not possible without some help. I use reflection to solve this.

In the derived class include a function to populate from a base class using reflection.

public class DerivedClass : BaseClass
{
    public DerivedClass PopulateBaseClass(object baseClass)
    {

        var parentProperties = baseClass.GetType().GetProperties();
        var childProperties = this.GetType().GetProperties();

        foreach (var parentProperty in parentProperties)
        {
            foreach (var childProperty in childProperties)
            {
                if (parentProperty.Name == childProperty.Name && parentProperty.PropertyType == childProperty.PropertyType)
                {
                    childProperty.SetValue(this, parentProperty.GetValue(baseClass));
                    break;
                }
            }
        }

        return this;
    }
    public string SomeDerivedClassProperty { get; set; }
}

Convert list of BaseClass to list of DerivedClass like so

   List<BaseClass> items = new List<BaseClass>();
   // populate some base class items
   List<DerivedClass> derivedItems = items.Select(i => new DerivedClass().PopulateBaseClass(i)).ToList();

Alternatively you could use Json to take care of the heavy lifting if your derived class has not defined alternate property names using JsonProperty

List<DerivedClass> derivedItems = JsonConvert.DeserializeObject<List<DerivedClass>>(JsonConvert.SerializeObject(items));

Upvotes: 1

Camilo Terevinto
Camilo Terevinto

Reputation: 32068

Not tested, but this should work:

List<ParentClass> parentList = ServiceApi.GetParentClassList().ToList();
List<ChildClass> childList = parentList
    .Where(x => x is ChildClass)
    .Select(x => x as ChildClass)
    .ToList();

You could also use OfType as mentioned in the comments:

List<ChildClass> childList = parentList
    .OfType<ChildClass>()
    .ToList();

See this .NET Fiddle: https://dotnetfiddle.net/gDsNKi

Upvotes: 1

Eric Olsson
Eric Olsson

Reputation: 4913

If you are willing to lose items where the cast fails, you can use the OfType() method to return only the items for which the cast succeeds.

var children = parentList.OfType<ChildClass>().ToList();

Upvotes: 2

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726579

Cast<T> method applies cast operation to all elements of the input sequence. It works only if you can do the following to each element of the sequence without causing an exception:

ParentClass p = ...
ChildClass c = (ChildClass)p;

This will not work unless p is assigned an instance of ChildClass or one of its subclasses. It appears that in your case the data returned from the server API contains objects of ParentClass or one of its subclasses other than ChildClass.

You can fix this problem by constructing ChildClass instances, assuming that you have enough information from the server:

List<ChildClass> childList = parentList
    .Select(parent => new ChildClass(parent.Name, ... /* the remaining fields */))
    .ToList();

Upvotes: 4

Related Questions