11

A simplified example of what i'm trying to achieve looks like this:

public class Animal
{
    public virtual Teeth teeth {get;set;}
}

public class Mouse : Animal
{
    public override SmallTeeth teeth {get; set;} // SmallTeeth Inherits from Teeth
}

This obviously doesn't work as teeth must be same type as in the Animal class to be overriden in the Mouse class. But can something like this be achieved where I would be allowed to use the more derived type in any functions that are inherited from Animal? For example if the Animal class contained a function to bite:

public void Bite()
{
    teeth.bite()
    Console.WriteLine("Ouch")
} 

I could call the Bite() function inherited from Animal and it would use the Mouse class' field of type SmallTeeth. Is this possible? and is it the best way to do what i'm trying to do? If not, what would be the correct approach to this problem?

Markus Safar
  • 6,324
  • 5
  • 28
  • 44
Danny Herbert
  • 2,002
  • 1
  • 18
  • 26
  • @gyttt1: If i remember correctly this is something to do with the covariance concepts. You can override the usual way with Teeth, and your implementation can still return an object of type SmallTeeth – Pramod Mangalore Feb 21 '16 at 15:04

2 Answers2

18

NOTE: This answer was written in 2016. Return type covariance has (amazingly!) been added to C# in the years since. Keep that in mind when reading this answer.


The feature you want is called return type covariance, and C# does not support it. (C++ does, incidentally.)

The usual case made for covariant return types is:

abstract class Animal
{
    public abstract Cage GetCage();
}
public class Fish : Animal
{
    public override Aquarium GetCage() { ... }
}

This is not legal, but if it were legal it would be safe. That is, if you have an Animal in hand and you ask for a cage, you get one, even if it is a fish. Why? Because an aquarium is a kind of cage.

What you propose though is not only illegal, it's unsafe:

Animal animal = new Mouse();
animal.Teeth = new TRexTeeth();

The contract is that the setter can be called with any kind of tooth. By making the derived class more restrictive in what it can accept, you violate the contract of the base class.

So don't do that.

There are a bunch of ways though that you can achieve what you want in C#.

Here's just one of them:

interface IAnimal
{
    Teeth Teeth { get; } // READ ONLY
}

class Mouse : IAnimal
{
    private SmallTeeth smallTeeth;
    public SmallTeeth Teeth 
    {
        get { return smallTeeth; }
    }

    Teeth IAnimal.Teeth { get { return this.Teeth; } }
}

Now if you cast a mouse to IAnimal you get the property that returns Teeth, and if you use a mouse normally you get the property that returns SmallTeeth.

I describe another way to solve this problem here:

Does C# support return type covariance?

And the generic solution given in the other answer also works, though personally I prefer to keep generics out of it unless necessary.

Do a search on "return type covariance in C#" for more information on this pattern.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    From a linguistic perspective, use of generics is also a good thing and often ignored. Confusion caused by poor naming choices is sadly a daily occurrence for software developers encountering a new codebase for the first time (I've experienced this both as a new joiner and as someone onboarding a new colleague) If I saw that `GetCage` returns `Aquarium` in a PR I'd ask the author to rethink naming. `Enclosure` is the most generic word I can think of at present. Aquariums, Cages, Tanks, Hutches would all qualify as animal enclosures and would be valid return types for `GetEnclosure` – user1007074 Jan 20 '21 at 10:24
  • 1
    C# 9 recently added Covariant returns: https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/ – wmeyer Jan 28 '21 at 12:59
  • @wmeyer: Thanks for the note; I have updated the answer. – Eric Lippert Jan 28 '21 at 19:47
4

Actually yes. You could but should not use generics for that purpose including a type constraint (see comments below and the post of Eric Lippert who explains in detail, what you are wanting or trying to achieve in your situation):

public class Animal<T>  where T : Teeth
{
    public virtual T teeth {get;set;}
}

public class Mouse : Animal<SmallTeeth>
{
    public override SmallTeeth teeth {get; set;} // SmallTeeth Inherits from Teeth
}
Community
  • 1
  • 1
Markus Safar
  • 6,324
  • 5
  • 28
  • 44
  • 3
    This is interesting, but I imagine that introducing generics here will create complexity / problems elsewhere. – Jonathon Reinhart Feb 21 '16 at 15:13
  • 4
    Good solution, but the drawback should be mentioned as well: The animals no longer have a common supertype (other than `object`). For example, you can't just create a `List` any more. – Heinzi Feb 21 '16 at 15:13
  • 2
    yes @Heinzi, this is a good answer but unfortunately that drawback is a deal breaker! – Danny Herbert Feb 21 '16 at 15:15
  • 1
    You can get around the lack of a common base by creating a regular `Animal` base class (so `Animal : Animal`), and you can give `Animal` a `public Teeth UntypedTeeth` property that returns the same object as the other property. You can then use both the general type or the specific type as needed. – Dave Cousineau Feb 22 '16 at 16:25
  • You can have them inherit from an interface as a workaround - something like `public interface IAnimal ... class Animal : IAnimal ... class Mouse : Animal` – Nick Bull Aug 21 '19 at 10:26