Reputation: 111
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
Reputation: 35155
Generally speaking casting is suspicious and best avoided. It's a great signal that something isn't right.
To fix the code you need to change
public List<Book> Books { get; set; }
to
public List<IBook> Books { get; set; }
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
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
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
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