0

Ok, so here's the problem. I have an interface IBook, which includes property Name. There is two classes which inherit from the IBook and add their own property Genre. I wanna create a Dictionary or a List and add all kinds of books there and access them by string and their properties so I made it Dictionary. In the example, I can access books["LOTR"].Name but not books["LOTR"].Genre, propably because Name is property of the IBook interface but Genre is property of the class that inherits from the IBook.

Is it possible to make the Dictionary or List work with the interface type and still be able to access all the inheriting class properties as well, or should I use an array or something?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp124
{
interface IBook
{
    string Name { get; set; }
}

public class FantasyBook:IBook
{
    string name;
    string genre;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
    public string Genre
    {
        get { return genre; }
        set { genre = value; }
    }
}
public class HorrorBook : IBook
{
    string name;
    string genre;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
    public string Genre
    {
        get { return genre; }
        set { genre = value; }
    }
}
class Program
{
    static void Main(string[] args)
    {
        FantasyBook LordOfTheRings = new FantasyBook();
        HorrorBook Frankenstein = new HorrorBook();
        Dictionary<string, IBook> books = new Dictionary<string, 
IBook>();

        books.Add("LOTR", LordOfTheRings);
        books.Add("Frankenstein", Frankenstein);
        books["LOTR"].Name = "Lord Of The Rings";
        books["LOTR"].Genre = "Fantasy";
        Console.ReadLine();
    }
}
}
  • 2
    if they all have Name and Genre, why don't you add genre to the interface then? You can access all the properties that are not in the interface if you cast your `(books["LOTR"] as FantasyBook).Genre` – Charles Dec 12 '19 at 21:33
  • 2
    "Is it possible to make the Dictionary or List work with the interface type and still be able to access all the inheriting class properties as well" nope not possible. "or should I use an array or something?" the problem is not with the collection that contains the IBook so nope again using an array isn't going to solve anything. the solution to your problem could simply be solved by making `Genre` a part of the `IBook` deifnition as it makes sense to do so. why did you not consider that in the first place? what was the design reasoning? – Ousmane D. Dec 12 '19 at 21:34
  • 1
    You can introduce base abstract class, something like `BaseBook` with `Name` property (or add it to the interface) and than cast a dictionary value to this type to access the `Name` – Pavel Anikhouski Dec 12 '19 at 21:34
  • This is just an example about a situation in another project i'm working on. So there is lots of different kinds of inheriting classes which add different types of properties – TheMostEpic Dec 12 '19 at 21:52
  • 1
    It's bothersome that you are naming interfaces by genre, and have a 'Genre' property. Smells bad. – RamblinRose Dec 12 '19 at 21:56
  • Charles, that is pretty near of what I'm trying to do here. Would it be possible to access the inheriting class properties with just the string "LOTR" without knowing the type of the actual class? – TheMostEpic Dec 12 '19 at 21:57
  • You could test `books["LOTR"] is FantasyBook` and then cast it. But this is probably a terrible idea, very anti-OO, especially in light of the fundamental OO modeling flaw that @RamblinRose pointed out. – StackOverthrow Dec 12 '19 at 22:35
  • 1
    BTW, take a look at [auto properties](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties). This question smells like homework assigned by a professor who learned C# in 2003. – StackOverthrow Dec 12 '19 at 22:38
  • Like I said, this is just an example (maybe a bad one) of a problem i'm having with another project with different interface, classes and multiple properties. Maybe other kind of approach is needed to solve this. Thanks for the auto-property hint, will use that! – TheMostEpic Dec 13 '19 at 08:16

2 Answers2

3

An alternative approach is to add another layer of interface with Genre and use pattern matching for accessing the properties:

interface IBook
{
    string Name { get; set; }
}

interface IBookWithGenre : IBook
{
    string Genre { get; set; }
}

public class FantasyBook : IBookWithGenre
{
    public string Name { get; set; }
    public string Genre { get; set; }
}
public class HorrorBook : IBookWithGenre
{
    public string Name { get; set; }
    public string Genre { get; set; }
}

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

class Program
{
    static void Main(string[] args)
    {
        FantasyBook LordOfTheRings = new FantasyBook();
        HorrorBook Frankenstein = new HorrorBook();
        SimpleBook abook = new SimpleBook();
        var books = new Dictionary<string, IBook>
        {
            { "LOTR", LordOfTheRings },
            { "Frankenstein", Frankenstein },
            { "Simple", abook },
        };
        books["LOTR"].Name = "Lord Of The Rings";
        if (books["LOTR"] is IBookWithGenre withGenre)
        {
            withGenre.Genre = "Fantasy";
        }
        Console.ReadLine();
    }
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
1

The comments are pretty much on point - you cannot do that as the compiler will examine available members on the IBook (since you declared it) and will not let you shoot yourself in the foot by trying to access a property that's not defined there. This is static type checking.

But let's for a second imagine you don't care about type safety and performance. It turns out, you have an option then. Well, sort of...as you will still have to give up your specific IBook for dynamic

interface IBook {
    string Name { get; set; }
}

public class FantasyBook : IBook
{
    public string Name { get; set; }
    public string Genre { get; set; }
}
public class HorrorBook : IBook
{
    public string Name {get;set;}
    public string Genre {get;set;}
}

public class BadaBook : IBook // so I added this new class that does not implement Genre to illustrate a point
{
    public string Name { get; set; }
}
static void Main(string[] args)
    {
        var LordOfTheRings = new FantasyBook();
        var Frankenstein = new HorrorBook();
        var Badaboom = new BadaBook();
        Dictionary<string, dynamic> books = new Dictionary<string, dynamic>();

        books.Add("LOTR", LordOfTheRings);
        books.Add("Frankenstein", Frankenstein);
        books.Add("Badaboom", Badaboom);
        books["LOTR"].Name = "Lord Of The Rings";
        books["LOTR"].Genre = "Fantasy";
        books["Badaboom"].Name = "We can easily assign Name as it is defined. No problem here";
        books["Badaboom"].Genre = "But we will miserably fail here"; // RuntimeBinderException: 'UserQuery.BadaBook' does not contain a definition for 'Genre'

        Console.ReadLine();
    }

Check out dynamic for further reading. It comes with the risks outlined in my example as well as performance penalties. It's not bad per se, it just needs to be taken in moderation.

timur
  • 14,239
  • 2
  • 11
  • 32
  • timur's answer should work. Thank you so much! I understand i need to be very careful with the dynamic, not to create a runtime error problem. – TheMostEpic Dec 13 '19 at 11:49