简体   繁体   中英

How to allow Class Property be of multiple/flexible types in c#?

In C# I have three classes: Person, Cat, and Dog.

Both the Cat and Dog classes have the method Eat().

I want the Person class to have a property 'Pet'.

I want to be able to call the Eat method of both the Cat and Dog via the Person via something like Person.Pet.Eat() but I can't because the Pet property needs to be either of type Cat or Dog.

Currently I'm getting round this with two properties in the Person class: PetDog and PetCat.

This is okay for now, but if I wanted a 100 different types of animal as pets then I don't really want to have 100 different Pet properties in the Person class.

Is there a way round this using Interfaces or Inheritance? Is there a way I can set Pet to be of type Object but still access the properties of whichever animal class is assigned to it?

You could have the pets derive from a common base class:

public abstract class Animal
{
    protected Animal() { }

    public abstract void Eat();
}

And then have Cat and Dog derive from this base class:

public class Cat: Animal
{
    public override void Eat()
    {
        // TODO: Provide an implementation for an eating cat
    }
}

public class Dog: Animal
{
    public override void Eat()
    {
        // TODO: Provide an implementation for an eating dog
    }
}

And your Person class will have a property of type Animal :

public class Person
{
    public Animal Pet { get; set; }
}

And when you have an instance of Person:

var person = new Person 
{
    Pet = new Cat()
};
// This will call the Eat method from the Cat class as the pet is a cat
person.Pet.Eat();

You could also provide some common implementation for the Eat method in the base class to avoid having to override it in the derived classes:

public abstract class Animal
{
    protected Animal() { }

    public virtual void Eat()
    {
        // TODO : Provide some common implementation for an eating animal
    }
}

Notice that Animal is still an abstract class to prevent it from being instantiated directly.

public class Cat: Animal
{
}

public class Dog: Animal
{
    public override void Eat()
    {
        // TODO: Some specific behavior for an eating dog like
        // doing a mess all around his bowl :-)

        base.Eat();
    }
}

Is there a way round this using Interfaces or Inheritance?

Yes.

Interfaces: make an interface IEat or IPet or whatever concept you want to represent. Make the interface have an Eat method. Have Cat and Dog implement this interface. Have the Pet property be of that type.

Inheritance: Make an abstract base class Animal or Pet or whatever concept you want to represent. Make an abstract method Eat on the base class. Have Cat and Dog inherit from this base class. Have the Pet property be of that type.

What is the difference between these two?

Use interfaces to model the idea "X knows how to do Y". IDisposable, for example, means "I know how to dispose of the important resource that I am holding onto". That is not a fact about what the object is , it is a fact about what the object does .

Use inheritance to model the idea of "X is a kind of Y". A Dog is a kind of Animal.

The thing about interfaces is you can have as many of them as you want. But you only get to inherit directly from one base class, so you have to make sure you get it right if you're going to use inheritance. The problem with inheritance is that people end up making base classes like "Vehicle" and then they say "a MilitaryVehicle is a kind of Vehicle" and "A Ship is a kind of Vehicle" and now you're stuck: what is the base class of Destroyer? It is both a Ship and a MilitaryVehicle and it can't be both . Choose your "inheritance pivot" extremely carefully .

Is there a way I can set Pet to be of type Object but still access the properties of whichever animal class is assigned to it?

Yes, in C# 4 there is, but do not do so . Use interfaces or inheritance.

In C# 4 you can use "dynamic" to get dynamic dispatch at runtime to the Eat method on the object that is in Pet.

The reason you don't want to do this is because this will crash and die horribly should someone put a Fruit or a Handsaw in the Pet property and then try to make it Eat. The point of compile-time checks is to decrease program fragility. If you have a way to make more compile-time checks, use it.

Using an interface;

abstract class Animal
{
    public virtual void DoSomething() { }
}

interface ICanEat
{
    void Eat();
}

class Dog : Animal, ICanEat
{
    public void Eat()
    {
        Console.Out.WriteLine("Dog eat");
    }
}

class Cat : Animal, ICanEat
{
    public void Eat()
    {
        Console.Out.WriteLine("Cat eat");
    }
}

class Person
{
    public Animal MyAnimal { get; set; }
}

Then you can call

(person.MyAnimal as ICanEat).Eat();

performing the correct null-checks as you go.

Use an abstract class.

public abstract class Pet { public abstract void Eat(); }

public class Dog : Pet { }
public class Cat : Pet { }

public class Person {
    public Pet Pet;
}

I'm sure you can do the rest.

Why not create a base class of type Pet

public abstract class Pet{

    public abstract void Eat();

}

Have this cat and dog inherit from an interface IPet

public interface IPet
{
    bool hungry();
    void Eat();
}

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