A generic method can use contravariant/covariant types?

后端 未结 3 442
梦如初夏
梦如初夏 2020-12-31 19:00

I\'m writting a generalized method to use it in a special task at a T4 template. The method should allow me to use specialized types from a general interface. I thought abou

3条回答
  •  夕颜
    夕颜 (楼主)
    2020-12-31 19:54

    This question is quite confusing. Let me see if I can clarify it.

    When I try to implement IGreatInterface the compiler flags an error for aMethodBeta() because I've made that method using a subtype of IAnInterface I want to implement that method like this: Object aMethodBeta(AnInterestingClass parameter).

    That's not legal. Simplifying somewhat:

    class Food {}
    class Fruit : Food {}
    class Meat : Food {}
    interface IEater
    {
        void Eat(Food food);
    }
    class Vegetarian : IEater
    {
        public void Eat(Fruit fruit);
    }
    

    Class Vegetarian does not fulfill the contract of IEater. You should be able to pass any Food to Eat, but a Vegetarian only accepts Fruit. C# does not support virtual method formal parameter covariance because that is not typesafe.

    Now, you might then say, how about this:

    interface IFruitEater
    {
        void Eat(Fruit fruit);
    }
    class Omnivore : IFruitEater
    {
        public void Eat(Food food);
    }
    

    Now we have got type safety; Omnivore can be used as an IFruitEater because an Omnivore can eat fruit, as well as any other food.

    Unfortunately, C# does not support virtual method formal parameter type contravariance even though doing so is in theory typesafe. Few languages do support this.

    Similarly, C# does not support virtual method return type variance either.

    I'm not sure if that actually answered your question or not. Can you clarify the question?

    UPDATE:

    What about:

    interface IEater
    {
        void Eat(T t) where T : Food;
    }
    class Vegetarian : IEater
    {
        // I only want to eat fruit!
        public void Eat(Fruit food) { }
    }
    

    Nope, that's not legal either. The contract of IEater is that you will provide a method Eat that can take any T that is a Food. You cannot partially implement the contract, any more than you could do this:

    interface IAdder
    {
        int Add(int x, int y);
    }
    class Adder : IAdder
    {
        // I only know how to add two!
        public int Add(2, int y){ ... }
    }
    

    However, you can do this:

    interface IEater where T : Food
    {
        void Eat(T t);
    }
    class Vegetarian : IEater
    {
        public void Eat(Fruit fruit) { }
    }
    

    That is perfectly legal. However, you cannot do:

    interface IEater where T : Food
    {
        void Eat(T t);
    }
    class Omnivore : IEater
    {
        public void Eat(Food food) { }
    }
    

    Because again, C# does not support virtual method formal parameter contravariance or covariance.

    Note that C# does support parametric polymorphism covariance when doing so is known to be typesafe. For example, this is legal:

    IEnumerable fruit = whatever;
    IEnumerable food = fruit;
    

    A sequence of fruit may be used as a sequence of food. Or,

    IComparable fruitComparer = whatever;
    IComparable appleComparer = fruitComparer;
    

    If you have something that can compare any two fruits then it can compare any two apples.

    However, this kind of covariance and contravariance is only legal when all of the following are true: (1) the variance is provably typesafe, (2) the author of the type added variance annotations indicating the desired co- and contra-variances, (3) the varying type arguments involved are all reference types, (4) the generic type is either a delegate or an interface.

提交回复
热议问题