简体   繁体   中英

How to access List<Interface> object properties when it's class is inherited from interface?

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

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM