dan
dan

Reputation: 5784

How do I properly return a List of Interfaces type?

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();

Upvotes: 11

Views: 13741

Answers (4)

Gabe
Gabe

Reputation: 86718

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.

Upvotes: 13

Matthew Flaschen
Matthew Flaschen

Reputation: 284786

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.

Upvotes: 2

cdhowie
cdhowie

Reputation: 168988

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.

Upvotes: 2

Cheng Chen
Cheng Chen

Reputation: 43523

I think you are working on old versions instead of c# 4.0 where we have covariance and contravariance. So you can only convert the list element by element.

Upvotes: 0

Related Questions