In C#, is it possible to cast a List<Child> to List<Parent>?
Asked Answered
P

6

76

I want to do something like this:

List<Child> childList = new List<Child>();
...
List<Parent> parentList = childList;

However, because parentList is a List of Child's ancestor, rather than a direct ancestor, I am unable to do this. Is there a workaround (other than adding each element individually)?

Parentage answered 22/11, 2009 at 4:22 Comment(1)
It's not an exact duplicate, but there's a very similar question here: https://mcmap.net/q/266819/-ienumerable-lt-t-gt-conversionMetatherian
M
68

Casting directly is not allowed because there's no way to make it typesafe. If you have a list of giraffes, and you cast it to a list of animals, you could then put a tiger into a list of giraffes! The compiler wouldn't stop you, because of course a tiger may go into a list of animals. The only place the compiler can stop you is at the unsafe conversion.

In C# 4 we'll be supporting covariance and contravariance of SAFE interfaces and delegate types that are parameterized with reference types. See here for details:

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/

Morose answered 22/11, 2009 at 17:9 Comment(2)
Even worse, if the compiler let you do that, your list would end up smaller than expected because the tiger would eat a few of the giraffes.Irk
Does anything change if the type parameter for the "parent" is an interface? It appears that a covariant collection interface solves OP's original problem.Marguritemargy
S
102

Using LINQ:

List<Parent> parentList = childList.Cast<Parent>().ToList();

Documentation for Cast<>()

Schramke answered 22/11, 2009 at 4:23 Comment(9)
Note that this creates a copy of childList, exactly like the not-using-LINQ version by @Andre Pena.Packer
List<Parent> parentList = childList.OfType<Parent>().ToList(); also works, and is preferable in cases where you are less sure of the content of the starting list.Lymn
@dtb, you get a new list, but I suspect there's a good chance that the objects in the list are the same objects.Lymn
If Child inherits from Parent, then you can be 100% certain that the cast will work anyway.Schramke
Besides, C# 4 will bring in more convenient conversion, msdn.microsoft.com/en-us/library/ee207183(VS.100).aspxMeanie
lextm: not for lists. List<>s are neither covariant nor contravariant.Schramke
Maybe I am missing something, but this exactly what the post author tried to do, which does NOT work. At run time you will get a System.InvalidCastException, because the type safe collection does not allow the cast. (Using Linq doesn't change a thing). See what Eric Lippert wrote.Cailean
The thing you are missing is that this construction creates a brand new list, which will not throw any exceptions at run time.Schramke
@Schramke true, so you can use the new list of Parent objects to iterate over,etc. But the List<Parent> is a new object and not a reference to the original, so if you remove one of its elements, the original List<Child> will not be modified, for example.Subtangent
M
68

Casting directly is not allowed because there's no way to make it typesafe. If you have a list of giraffes, and you cast it to a list of animals, you could then put a tiger into a list of giraffes! The compiler wouldn't stop you, because of course a tiger may go into a list of animals. The only place the compiler can stop you is at the unsafe conversion.

In C# 4 we'll be supporting covariance and contravariance of SAFE interfaces and delegate types that are parameterized with reference types. See here for details:

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/

Morose answered 22/11, 2009 at 17:9 Comment(2)
Even worse, if the compiler let you do that, your list would end up smaller than expected because the tiger would eat a few of the giraffes.Irk
Does anything change if the type parameter for the "parent" is an interface? It appears that a covariant collection interface solves OP's original problem.Marguritemargy
K
17

Back in 2009 Eric teased us that things would change in C# 4. So where do we stand today?

The classes used in my answer can be found at the bottom. To make this easier to follow, we will use a Mammal class as "parent", and Cat and Dog classes as "children". Cats and dogs are both mammals, but a cat is not a dog and a dog is not a cat.

This still isn't legal, and can't be:

List<Cat> cats = new List<Cat>();

List<Mammal> mammals = cats;

Why not? Cats are mammals, so why can't we assign a list of cats to a List<Mammal>?

Because, if we were allowed to store a reference to a List<Cat> in a List<Mammal> variable we would then be able to compile the following code to add a dog to a list of cats:

mammals.Add(new Dog());

We mustn't allow that! Remember, mammals is just a reference to cats. Dog does not descend from Cat and has no business being in a list of Cat objects.

Starting with .NET Framework 4, several generic interfaces have covariant type parameters declared with the out Generic Modifier keyword introduced in C# 4. Amongst these interfaces is IEnumerable<T> which of course is implemented by List<T>.

That means we can now cast a List<Cat> to an IEnumerable<Mammal>:

IEnumerable<Mammal> mammalsEnumerable = cats;

We can't add a new Dog to mammalsEnumerable because IEnumerable<out T> is a "read-only" interface i.e. it has no Add() method, but we can now use cats wherever a IEnumerable<Mammal> can be consumed. For example, we can concatenate mammalsEnumerable with a List<Dog> to return a new sequence:

void Main()
{
    List<Cat> cats = new List<Cat> { new Cat() };
    IEnumerable<Mammal> mammalsEnumerable =
        AddDogs(cats); // AddDogs() takes an IEnumerable<Mammal>
    Console.WriteLine(mammalsEnumerable.Count()); // Output: 3. One cat, two dogs.
}

public IEnumerable<Mammal> AddDogs(IEnumerable<Mammal> parentSequence)
{
    List<Dog> dogs = new List<Dog> { new Dog(), new Dog() };
    return parentSequence.Concat(dogs);
}

Class definitions:

public abstract class Mammal { }

public class Cat: Mammal { }

public class Dog : Mammal { }
Kure answered 14/3, 2018 at 13:41 Comment(2)
++ for the only one to find the crux of the problem: read-only interfaceRestrained
Gist? This works with IEnumerable<T> but not List<T>Marguritemargy
P
-1

Don't do this:

List<Parent> parentList = childList.Cast<Parent>().ToList();

A child class should not be used as parent, if you want to pass a list as parameter of a method that receives a list<parent>, you need to change the method parameter using IEnumerable, more info IEnumerable.

public void methodA(IEnumerable<parent> param){}

Now you can use this method like this:

var oListChild = new List<child>();
methodA(oListChild);
Program answered 31/10, 2022 at 4:12 Comment(1)
List<T> already is (implements) IEnumerable<T> (which in term implements IEnumerable). The documentation link you included is useful, but this answer doesn't explain how one would create (or cast) an IEnumerable<TChild> to IEnumerable<TParent>.Marguritemargy
F
-2

yes, you can do it like

var result = List.And(x => x.Parent.All(b => b.ParentId == value));
From answered 12/8, 2016 at 6:7 Comment(1)
This seems to be related to an ORM object hierarchy. More context is needed for a useful example.Marguritemargy
A
-3

You can do this by using a Linq approach of the apply extension method, i.e.:

List<Parent> parentList = childList.Cast<Parent>().ToList();
Au answered 18/10, 2014 at 9:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.