How do I properly return a List of Interfaces type?

10,558

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.

Share:
10,558

Related videos on Youtube

dan
Author by

dan

Updated on May 10, 2022

Comments

  • dan
    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
    cdhowie over 13 years
    In 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
    Gabe over 13 years
    Since 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, because IList<T> is an invariant interface (T is both an input and output of its functions).
  • Gabe
    Gabe over 13 years
    Good 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 the Enumerable class and call it statically, but that's probably not worth the effort.
  • Cheng Chen
    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
    Gabe over 13 years
    The 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
    Cheng Chen over 13 years
    @Gabe: Thanks very much. Appreciate for your help!
  • Cheng Chen
    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
    Gabe over 13 years
    It 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 that list[0].Name != books[0].Name