10

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.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
Roka545
  • 3,404
  • 20
  • 62
  • 106
  • Possible duplicate of [Is it possible to assign a base class object to a derived class reference with an explicit typecast in C#?](https://stackoverflow.com/questions/729527/is-it-possible-to-assign-a-base-class-object-to-a-derived-class-reference-with-a) – Rufus L Jun 20 '17 at 18:22
  • 2
    @RufusL I don't think that duplicate is exactly a duplicate. The OP's question is not really the same – Camilo Terevinto Jun 20 '17 at 18:24
  • 1
    Possible duplicate of [C# : Converting Base Class to Child Class](https://stackoverflow.com/questions/16534253/c-sharp-converting-base-class-to-child-class) – Lews Therin Jun 20 '17 at 18:25
  • 1
    @CamiloTerevinto You are correct, but it is a duplicate of the question I flagged. – Lews Therin Jun 20 '17 at 18:26
  • 1
    @CamiloTerevinto Thanks for the clarification! Agreed, but I'll leave the close vote since Lews Therin found the right one. – Rufus L Jun 20 '17 at 18:29

5 Answers5

24

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 Cats. 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.

So in that 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.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • Great answer, but how would I do this if the 'Dog' type would only be known at runtime Type dogType = typeof(Dog) ? – AlexandruC Jan 18 '19 at 15:12
  • 1
    @AlexandruC I'd regard that as a misuse of the type system, the need for which likely results from a flawed design choice made much earlier, and usually indicates a need to convert an inheritance design to composition. – Joel Coehoorn Jan 18 '19 at 16:31
  • The OfType is a great suggestion. Thanks for mentioning that! – MichielDeRouter Jul 30 '20 at 10:19
  • Wonderful, just what I needed to get my refactoring working. – AndyUK Jun 14 '23 at 09:24
4

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();
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • This is really the worst answer, not sure why the OP picked this as accepted – Camilo Terevinto Jun 20 '17 at 22:11
  • @CamiloTerevinto My best guess is that this is the only answer that shows OP how to deal with the situation when there are no `ChildClass` objects in the `childList`, which is very likely to happen when you receive DTO objects from the server. – Sergey Kalinichenko Jun 20 '17 at 23:53
  • That's not completely correct, my answer also deals with empty lists and lists without ChildClass objects. In fact, all the other 3 answers deal with that problem – Camilo Terevinto Jun 21 '17 at 11:01
  • @CamiloTerevinto That's just two corner cases. It looks like OP gets objects of unknown subclasses from the server, and needs to convert them to objects of known subclasses on the client, rather than filtering them out by type. – Sergey Kalinichenko Jun 21 '17 at 11:03
  • If that's the case, I'd rather have a foreach where ChidlClass is added to the list and everything else is converted to a new ChildClass with the base properties. Why? Because the way you wrote it, you never get any ChildClass property at all, it'd be the same as just leaving as ParentClass – Camilo Terevinto Jun 21 '17 at 11:14
2

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();
Eric Olsson
  • 4,805
  • 32
  • 35
1

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

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • In this example (and the Fiddle), this only works if the List has a ChildClass already in it. In the event the List DOES NOT have any ChildClasses inside of it, it will return 0. Is it possible to handle the case where List ONLY has BaseClass objects in it? Then, take that list and convert it to a List ? – Roka545 Jun 20 '17 at 18:49
  • 1
    @Roka545 in the case there are no ChildClass instances in the list, the Where will return a new `List`, empty. (basically, it instantiantes a new list and add any objects whose filter return true, so you always have a new list, regardless of the filter) – Camilo Terevinto Jun 20 '17 at 18:50
0

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));
clamchoda
  • 4,411
  • 2
  • 36
  • 74
  • I think the question is asking how to convert the type of the _list_, not the objects within. What would be the use of converting a base instance to a derived instance with none of the derived-only members populated? What if there are members that can only be initialized through parameters passed to the constructor? – Lance U. Matthews Oct 19 '22 at 19:28