Search code examples
c#oopcontravariance

Why doesn't C# allow parameter type contravariance?


C# supports return type covariance, but why does it not support parameter type contravariance.

Imagine this example:

abstract class Animal
{
    public abstract void PlayWith(Toy toy);
}

class PlayfulMonkey : Animal
{
    public override void PlayWith(Object toy) // PlayfulMonkey can play with anything
    {
        // Monkey playing with its object...
    }
}

C# complier throws an error at you when trying this. But I feel like it would make so much sense to allow this.


Solution

  • A "why not" question presupposes that the feature is so obviously good that the designers should provide you a reason to not spend their time, money and effort to implement the feature. But that's not how language design works. The number of possible features is huge, and the onus is on the person requesting the feature to explain not just why it's a good feature, but why it is BETTER than every other feature the design team could be spending time on.

    People asked for return type covariance for literally two decades and it took that long to get to the top of the priority list. Why did it take that long to get return type covariance? Because (1) the design team considered it low priority, (2) the implementation is not trivial because the underlying type system in the CLR does not support virtual variance, and (3) there were always higher priority work items that we believed were more bang for buck.

    Would parameter type contravariance make sense? Sure. Is anyone other than you asking for it? Not to my knowledge. "It makes sense" is nowhere near a good enough reason; millions of features "make sense" but only a handful get implemented in any version.

    It's not the design team's job to explain to you why your pet feature isn't on top of their priority list. If you want this feature, the onus is on you to go to the roslyn github, find the existing design request if there is one, open a new one if there is not, and make a compelling argument that this is the most important thing that the design team should be spending time on. In your argument I recommend that you focus heavily on all the real-world problems that would be solved for professional line-of-business developers by adding this feature. I'm not aware of any, but maybe you are.

    ADDENDUM: I forgot to mention, if you do make an argument for this feature you should anticipate the following pushback. Suppose team Alpha writes this:

    class Alpha
    {
      public virtual void M(Giraffe g) { alpha implementation }
    }
    

    And then team Bravo says let's extend Alpha:

    class Bravo : Alpha
    {
      public override void M(Giraffe g) { bravo implementation }
    }
    

    and then team Charlie says let's extend Bravo:

    class Charlie : Bravo
    {
      public override void M(Giraffe g) { charlie implementation }
    }
    

    And then team Bravo says oh, wait, we can do better, our method actually accepts any mammal, let's use the new parameter type contravariance feature, and they change to:

    class Bravo : Alpha
    {
      public override void M(Mammal g) { bravo implementation }
    }
    

    Does team Charlie get a build break when they pick up the latest version of Bravo? Why or why not? If the Charlie implementation only handles giraffes, are they required to now handle any mammal just because Bravo can?

    C# designers try to avoid adding new versions of the "brittle base class" failure mode; they will if there is a compelling benefit, so again, identify that compelling benefit in your argument for why taking on a new form of versioning failure is acceptable.