Biojayc
Biojayc

Reputation: 111

Inheriting an interface with a List<Interface> as a property problems

I have two interfaces, one that contains a list of the other, and I want to have a class that implements the first interface but has a list of another class that implements the second interface instead of just a list of the interface. Example:

namespace TestInheritance
{
    public interface IBookShelf
    {
        long Stuff { get; set; }
        List<IBook> Books { get; set; }
    }
    public interface IBook
    {
        string Name { get; set; }
    }
    public class BookShelf : IBookShelf
    {
        public long Stuff { get; set; }
        public List<Book> Books { get; set; }
    }
    public class Book : IBook
    {
        public string Name { get; set; }
    }
}

Obviously it doesn't like this. Is there a correct way to do what I'm trying to do, or do I just have to make BookShelf have a List<IBook> and cast each IBook to a Book when I want to use it? Looking for a pattern that could help me out here. Thanks.

Upvotes: 3

Views: 700

Answers (4)

tmaj
tmaj

Reputation: 35155

  1. Generally speaking casting is suspicious and best avoided. It's a great signal that something isn't right.

  2. To fix the code you need to change

    public List<Book> Books { get; set; }
    

    to

    public List<IBook> Books { get; set; }
    
  3. You can and should generalise things more. What kind of book shelf doesn't allow other items on it? I'm yet to see one. Consider this:

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
public interface IShelf
{
    long Stuff { get; }
    List<IShelfItem> Items { get; }
}
public interface IShelfItem
{
    string Name { get; }
    string Thought {get; set;}//What do I think about it?
}
public class Shelf : IShelf
{
    public Shelf(long stuff, string color) 
    {
        Stuff = stuff;
        Items = new List<IShelfItem>();
        Color = color;
    }

    public long Stuff { get; private set; }
    public List<IShelfItem> Items { get; private set; }//Note, you can still add to the list.
    public string Color { get; set; }

}
public class Book : IShelfItem
{
    public Book( string name, string thought ) {
        Name = name;
        Thought = thought;
    }
    public string Name { get; private set; }//Books that are on shelves don't really change their name;
    public string Thought { get; set; }

    public string BookSpecificProperty { get; set; }
}


public static void Main()
{
    Shelf myShelf = new Shelf(42, "Dull boring grey");

    myShelf.Color = "Red";


    IShelf myIShelf = myShelf;
    //myIShelf.Color = "Red"; - 'IShelf' does not contain a definition for 'Color'

    myShelf.Items.Add( new Book("Title 1", "Such a great book" ) );

    //Eos pass

    var book = myShelf.Items.SingleOrDefault( i => i.Name == "Title 1" );
    if( book != null ) {
        book.Thought = "I used to think it was such a great book. It's just OK";
        //book.BookSpecificProperty = "X"; - 'IShelfItem' does not contain a definition for 'BookSpecificProperty'
    }
}

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1503809

It's not clear exactly what you are trying to do - and I suspect it's not even clear to you. Why do you particularly want BookShelf.Books to be a List<Book>? Why is it restricting itself to only include Book values rather than IBook values? I suggest you consider what you'd want to happen in this situation:

public class EvilBook : IBook
{
    public string Name { get; set; }
}

IBookShelf bookShelf = new BookShelf() { Books = new List<Book>() };
bookShelf.Books.Add(new EvilBook());

Nothing in that code is suspicious1, with the interfaces you've given - but it ends up adding an EvilBook into a List<Book>, which surely isn't right. I often find that if the compiler's stopping me from doing something, it's useful to think about what problems I might run into if it had let me go ahead with my bad idea.

You might want to consider making IBookShelf generic:

public interface IBookShelf<T> where T : IBook
{
    long Stuff { get; set; }
    List<T> Books { get; set; }
}

Then:

public class BookShelf : IBookShelf<Book>

... but it really depends on what you're trying to achieve.

You might also want to ask yourself whether you really need the IBook interface in the first place... and also whether you really want a public writable property for Books.


1 In terms the compiler would recognize, anyway :)

Upvotes: 2

undefined
undefined

Reputation: 34309

How about something like this:

namespace TestInheritance
{
    public interface IBookShelf <TBook> where TBook : IBook
    {
        long Stuff { get; set; }
        List<TBook> Books { get; set; }
    }
    public interface IBook
    {
        string Name { get; set; }
    }
    public class BookShelf : IBookShelf<Book>
    {
        public long Stuff { get; set; }
        public List<Book> Books { get; set; }
    }
    public class Book : IBook
    {
        public string Name { get; set; }
    }
}

Upvotes: 0

nemesv
nemesv

Reputation: 139808

You can make IBookShelf generic where you constrain the generic argument to IBook

Then you can have:

public interface IBookShelf<T> where T : IBook
{
    long Stuff { get; set; }
    List<T> Books { get; set; }
}

public class BookShelf : IBookShelf<Book>
{
    public long Stuff { get; set; }
    public List<Book> Books { get; set; }
} 

Upvotes: 4

Related Questions