Cast List<T> to List<Interface>
Asked Answered
J

10

151
public interface IDic
{
    int Id { get; set; }
    string Name { get; set; }
}
public class Client : IDic
{

}

How can I cast List<Client> to List<IDic>?

Jook answered 19/1, 2012 at 11:38 Comment(0)
C
316

You can't cast it (preserving reference identity) - that would be unsafe. For example:

public interface IFruit {}

public class Apple : IFruit {}
public class Banana : IFruit {}

...

List<Apple> apples = new List<Apple>();
List<IFruit> fruit = apples; // Fortunately not allowed
fruit.Add(new Banana());

// Eek - it's a banana!
Apple apple = apples[0];

Now you can convert a List<Apple> to an IEnumerable<IFruit> in .NET 4 / C# 4 due to covariance, but if you want a List<IFruit> you'd have to create a new list. For example:

// In .NET 4, using the covariance of IEnumerable<T>
List<IFruit> fruit = apples.ToList<IFruit>();

// In .NET 3.5
List<IFruit> fruit = apples.Cast<IFruit>().ToList();

But this is not the same as casting the original list - because now there are two separate lists. This is safe, but you need to understand that changes made to one list won't be seen in the other list. (Modifications to the objects that the lists refer to will be seen, of course.)

Catron answered 19/1, 2012 at 11:41 Comment(23)
like they're 'still in the building'!Tedtedd
What is the difference between doing the covariance tolist and doing new List<IFruit>(); then a foreach over the original list and adding each item to the IFruit list? In the foreach way... the object reference would be the same, correct? So... if that is true, it makes little sense to me personally that you can't just directly cast the whole list. ?Sackett
@RobertNoack: What do you mean by "the object reference"? The object reference of each element is the same, but that's not the same as casting the list reference itself. Suppose you could cast the reference, so that you had a reference of compile-time type List<IFruit> which was actually a reference to a List<Apple>. What would you expect to happen if you added a Banana reference to that List<IFruit>?Catron
Oh. I think I need to learn how to read. I thought the original answer said that the objects in the list would be deep copied and recreated which didn't make sense to me. But I clearly missed the part where only a new list is created with the same objects.Sackett
Why i can't use ToList and Cast (i'n use .net4). It not show in recommend. Or i missing some namespace?Tirzah
@TrươngQuốcKhánh: I have no idea what any of your code looks like, or whether you have a using directive for System.Linq, or what you're trying to call it on, I'm not sure how you expect me to be able to help. I suggest you do more research, and if you're still stuck, you ask a question with a minimal reproducible example.Catron
I can't see why you would expect/not expect a banana if you assigned an apply from your List<Apple>... // Eek - it's a banana! Apple apple = apples[0]; However, it is conceivable that List<IFruit> could contain either an apple or a banana, so you shouldn't be able to assign a list of IFruit to a list of Apple, in the same way you can't just assign a IFruit to an Apple without casting (and risk it being a banana!)... But why not the other way round?Hunsaker
@JamesJoyce: It's reasonable to expect a List<Apple> to only contain apples. Therefore it's surprising when it turns out it's a banana. It would basically be a violation of a sensible type system. (Bear in mind that you may not be aware of the code that's adding a banana.)Catron
@JamesJoyce: "But why not the other way round?" Precisely because that's how you end up adding a banana - if you could perform that assignment. Which statement in List<IFruit> fruit = apples; fruit.Add(new Banana()); would you expect not to work?Catron
Aaahhh - now I understand. Its the same list!!! My bad. I was forgetting that critical point. Thanks for taking the time!Hunsaker
You could always create your own custom typecast if that's the syntax you want to use. #1408189Gorrono
Guess down-voters were not happy that you didn't mention variance only applies to generic interface and delegates but not to generic classes:)Gritty
Coming from Java and given C# doesn't have type erasure, I would expect it to just throw an exception when trying to add a Banana. Basically when casting up to a List<IFruit> it would add type checks to ensure everything was the correct type.Hellenic
@thecodewarrior: The point of generics is broadly to make things safe at compile-time. C# generics are safer in this sense than C# arrays (which do have reference type variance, unfortunately).Catron
Btw, if you start off with a List of IFruit, can you add Apples and Bananas to it? How does that work?Silk
@KevinVictor: The same as if you have a variable of type IFruit and assign a reference to an Apple or Banana as the value. (I'm not sure what kind of answer you're looking for in terms of "How does that work".)Catron
@JonSkeet I've noticed that List<Apple> IS implicitly castable to IEnumerable<IFruit> - but not IList<IFruit>. Why's that?Rhombic
@Extragorey: Because IEnumerable<T> is a covariant interface, and IList<IFruit> isn't. It's safe to convert a List<Apple> to IEnumerable<IFruit> because all you can do is take things out of an IEnumerable<IFruit>, and every Apple is an IFruit - but with IList<T> there's a setter, so you could write fruitList.Add(new Banana()) which clearly isn't safe.Catron
@JonSkeet Ahh I see, so casting to IEnumerable is still useful for read-only usage.Rhombic
I really just don't like the fact List<Apple> can't be a param for a function asking for List<IFruit>... to me that seems like a language weakness.Northey
@Shiv: Would you prefer to wait until execution time for it to fail when that method then calls list.Add(new Banana());? (If the method only needs to read from the list, it should have a parameter of type IReadOnlyList<IFruit> instead, at which point it's fine.)Catron
@JonSkeet was trying to use List<T> readonly with the .ForEach capability as well... guess we can work around that. If readonly is required to give compile time safety I'm happy with that compromise. Makes a lot more sense.Northey
@Shiv: It's trivial to add your own ForEach extension method for IReadOnlyList<T> should you wish. I'd suggest reading ericlippert.com/2009/05/18/foreach-vs-foreach before doing so though.Catron
S
12

I too had this problem and after reading Jon Skeet's answer I modified my code from using List<T> to use IEnumerable<T>. Although this does not answer the OP's original question of How can I cast List<Client> to List<IDic>, it does avoid the need to do so and thus may be helpful to others who encounter this issue. This of course assumes that the code that requires the use of List<IDic> is under your control.

E.g.:

public void ProcessIDic(IEnumerable<IDic> sequence)
{
   // Implementation
}

Instead of:

public void ProcessIDic(List<IDic> list)
{
   // Implementation
}
Satyriasis answered 27/1, 2016 at 11:41 Comment(0)
T
10

A Cast iterator and .ToList():

List<IDic> casted = input.Cast<IDic>().ToList() will do the trick.

Originally I said covariance would work - but as Jon has rightly pointed out; no it won't!

And originally I also stupidly left off the ToList() call

Tedtedd answered 19/1, 2012 at 11:40 Comment(4)
Cast returns an IEnumerable<T>, not a List<T> - and no, covariance won't allow this conversion, because it would be unsafe - see my answer.Catron
From the page you linked to: "Only interface types and delegate types can have variant type parameters"Catron
@Jon - I'd realised the ToList() was missing before reading your comment; but yes as you've shown of course Covariance won't work! Doh!Tedtedd
Right. Covariance can still help, as it means you don't need the Cast call in .NET 4, so long as you specify the type argument to ToList.Catron
D
5

If you can use LINQ then you can do this...

List<Client> clientList = new List<Client>();
List<IDic> list = clientList.Select(c => (IDic)c).ToList();
Dulin answered 19/1, 2012 at 11:40 Comment(0)
D
5
List<Client> listOfA = new List<Client>();
List<IDic> list = listOfA.Cast<IDic>().ToList();
Delldella answered 19/1, 2012 at 11:44 Comment(0)
J
1

OfType

You can try something like:

        using (var dbContext = YourDatabaseContext())
        {
            var list = dbContext.Clients.Where(x => x.Happy)
                .OfType<IDic>()
                .ToList();
        }

See https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.oftype

Justiciary answered 19/9, 2020 at 15:6 Comment(0)
C
1

If you don't need to modify the contents of the original list, you can implicitly convert a List into a IReadOnlyList which will let you iterate over it's contents as IDics without creating a new list.

List<Client> myClients = new List<Client>();
myClients.Add(new Client());

IReadOnlyList<IDic> castedClients = myClients;
foreach(IDic val in castedClients)
{
    //do something;
}

The conversion can also occur while simply returning the list like so :

public IReadOnlyList<IDic> getClientsAsIDic()
{
    return myClients;
}
Cryptic answered 29/10, 2022 at 10:45 Comment(0)
S
0

Its only possible by creating new List<IDic> and transfering all elements.

Singsong answered 19/1, 2012 at 11:39 Comment(3)
Any comment why downvoted, since general meaning is the same as all other answers?Singsong
My guess would be you got downvoted because you said it was only possible to create a new list, but others have posted to the contrary... not my downvote though)Dulin
Well they do create new lists but not with new operator which doesn't change fact that they do.Singsong
R
0

In .Net 3.5, you can do the following:

List<ISomeInterface> interfaceList = new List<ISomeInterface>(list.Cast<ISomeInterface>());

The constructor for List in this case takes an IEnumerable.
list though is only convertible to IEnumerable. Even though myObj may be convertible to ISomeInterface the type IEnumerable is not convertible to IEnumerable.

Russophobe answered 4/1, 2017 at 4:1 Comment(1)
The issue is this makes a copy of the list, most of the time you want to do operations on the original listPestana
B
0

If you want to process the original list without creating a separated reference, you could define the generic method like this:

public void DoIterate<T>(List<T> myCollection) where T : IDic
{
   foreach (T item in myCollection)
   {
      //update a property of interface
      item.Name = "new Name";
   }
}

Calling this method above to process the list without having to cast specific object to interface:

List<Client> clients = new List<Client>();
DoIterate(clients);
Benefactor answered 12/9, 2022 at 21:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.