How do I properly return a List of Interfaces type?
Solution 1
The problem is that a List<IPublication>
is something that can hold any class that inherits from IPublication
. Since the compiler doesn't know that you won't try to put a Magazine
into the result of GetBooks()
, you have to write your function like this:
public List<IPublication> GetBooks()
{
List<IPublication> books = new List<IPublication>();
// put only books in here
return books;
}
If your function returns an immutable list that doesn't need to be accessed by index (and you're on C# 4), you can write it like this:
public IEnumerable<IPublication> GetBooks()
{
List<Book> books = new List<Book>();
return books;
}
If you can return an IEnumerable<T>
but you're using C# 3, you can do what cdhowie suggests:
public IEnumerable<IPublication> GetBooks()
{
List<Book> books = new List<Book>();
return books.Cast<IPublication>();
}
If you're using C# 2, it's better to just use the first method I proposed.
Solution 2
That's not a safe conversion. Imagine if someone put a magazine in your list of books.
public class Library
{
public List<Book> books = new List<Book>();
public List<IPublication> GetBooks()
{
return books;
}
}
Elsewhere,
Magazine mag = ...;
List<IPublication> pubs = someLibrary.GetBooks()
pubs.Add(mag);
In .NET 4, you can return a IEnumerable<IPublication>
, as described here, without creating any new objects. cdhowie also gives a good work-around for lower versions. Just drop the .ToList()
, and return an IEnumerable
.
I realize your particular case is safe. But the compiler can't verify that.
Solution 3
You will have to use your workaround unless you are on .NET 4.
Consider if someone were to do this:
public class Magazine : IPublication { }
// Somewhere...
var foo = something.GetBooks();
foo.Add(new Magazine());
If the runtime allowed the conversion from List<Book>
to List<IPublication>
, then you would be allowed to add magazines to a list of books! This breaks the type system, because if something still had a reference to the List<Book>
, it would rightly not expect a Magazine
to be in it, it would treat the Magazine
as though it were a book, and then runtime crashes (or worse -- data corruption or unsafe things) would ensue.
Related videos on Youtube
dan
Updated on May 10, 2022Comments
-
dan almost 2 years
With the following code, I receive an "unable to implicitly cast" compilation error on the "return books;" line.
I thought that because am returning a list of book objects that implement IPublication this should work fine?
public interface IPublication {} public class Book : IPublication {} public List<IPublication> GetBooks() { List<Book> books = new List<Book>(); return books; }
I note that if I return a single book as a single IPublication object it works fine. Introducing the
List<>
requires the explicit cast.As a workaround I am using:
return books.Cast<IPublication>().ToList();
-
cdhowie over 13 yearsIn your last example, you could also
return books.Cast<IPublication>();
on C# 3 and lower. It won't duplicate the list, but will instead return an enumerable that will cast each item to the interface type one by one, requiring almost no memory to do so. -
Gabe over 13 yearsSince
List<T>
is not an interface, variance will not help him here. Even if he switched to an interface, it still won't help him, becauseIList<T>
is an invariant interface (T
is both an input and output of its functions). -
Gabe over 13 yearsGood point, cdhowie. It only works on C# 3 and higher, though. Version 2 didn't have extension methods or a
Cast
function. Of course you can import theEnumerable
class and call it statically, but that's probably not worth the effort. -
Cheng Chen over 13 years@Gabe: sorry I didn't read the question carefully, it really looks like a co(ntra)variance question. But here I have another question: I think OP's code is correct using
return books.Cast<IPublication>().ToList()
, so what's his question? -
Gabe over 13 yearsThe problem with
books.Cast<IPublication>().ToList()
is that it creates a whole new duplicate list. He wants to know how to avoid having to create that copy. -
Cheng Chen over 13 years@Gabe: Thanks very much. Appreciate for your help!
-
Cheng Chen over 13 years@Gabe:Sorry why I tested locally but it won't create a copy? I set target framework to 4.0 and 3.5. Both not create a copy.
var list = books.Cast<IPublication>().ToList();(list[0] as Book).Name = "book1 changed"; Console.WriteLine(books[0].Name);
output is "book1 changed". -
Gabe over 13 yearsIt copies the list itself; it does not make copies of the items in the list. If you do
list[0] = new Book();
you will see thatlist[0].Name != books[0].Name