views:

129

answers:

6

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?

+1  A: 

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.

Alex
@Alex: Making the `Pet` property of type `ICanEat` will make the cast unnecessary. Also, you should *never* use the `as` operator for a casting-operation that is guaranteed to succeed, an explicit cast is what you need. `as` should be followed with a null-test before dereferencing because of the possibility of a failed cast.
Ani
+1  A: 

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.

Timwi
A: 

Why not create a base class of type Pet

public abstract class Pet{

    public abstract void Eat();

}
Josh
A: 

Have this cat and dog inherit from an interface IPet

public interface IPet
{
    bool hungry();
    void Eat();
}
Richard J. Ross III
+12  A: 

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();
    }
}
Darin Dimitrov
How does it know which class' Eat function to run when you call `person.Pet.Eat()`?
Abe Miessler
Depending on the actual type of the `Pet` property.
Darin Dimitrov
@Abe Miessler: It doesn’t at compile time. The decision is made at run-time, when it knows what type of instance is actually stored in `person.Pet`.
Timwi
So by having the abstract class as a member of the person class you can then assign any of the derived classes to that member? IE `Person.Pet = new Cat();` or `Person.Pet = new Dog();`?
Abe Miessler
@Abe Miessler, yes you can assign it to any derived class.
Darin Dimitrov
Nice example of OOP, +1
Jamie Keeling
+11  A: 

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.

Eric Lippert
If I have a Handsaw as a pet, I am probably insane enough to think it eats.
Jeff Yates
@Jeff: Indeed, if you don't know a hawk from a handsaw, you've got problems. My advice: wait for a southerly wind.
Eric Lippert
Thanks, this was very helpful and also witty. I'll vote it up once I get enough rep.
Caustix