Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A generic method can use contravariant/covariant types?

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 about the following signatures:

interface IGreatInterface {
    Object aMethodAlpha<U>(U parameter) where U : IAnInterface;
    Object aMethodBeta(IAnInterface parameter)
}

public class AnInterestingClass : IAnInterface{}

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

Method aMethodAlpha<U>() can be used but is not as clean as I want because my T4 has to generate some extra code. I (perhaps wrongly) propose that an implementation of that method, which has to be done by a T4, could be
Object aMethodAlpha<AnInterestingClass>(AnInterestingClass parameter).

I'm thinking that generic methods do not support contravariant types but I'm not sure; I suppose that It's the way the compiler prevents the coder to use a specific type having a method not defined in the general type...

  1. Does a generic method have to use the exact type when being implemented?
  2. Is there any trick to change this behavior?
like image 592
JPCF Avatar asked Nov 11 '11 20:11

JPCF


People also ask

What is generic covariance?

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.

How do you declare a generic interface as Contravariant?

You can declare a generic type parameter contravariant by using the in keyword. The contravariant type can be used only as a type of method arguments and not as a return type of interface methods. The contravariant type can also be used for generic constraints.

What is covariance and contravariance in generics in Java?

Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them.


2 Answers

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 t) where T : Food;
}
class Vegetarian : IEater
{
    // I only want to eat fruit!
    public void Eat<Fruit>(Fruit food) { }
}

Nope, that's not legal either. The contract of IEater is that you will provide a method Eat<T> 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<T> where T : Food
{
    void Eat(T t);
}
class Vegetarian : IEater<Fruit>
{
    public void Eat(Fruit fruit) { }
}

That is perfectly legal. However, you cannot do:

interface IEater<T> where T : Food
{
    void Eat(T t);
}
class Omnivore : IEater<Fruit>
{
    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> fruit = whatever;
IEnumerable<Food> food = fruit;

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

IComparable<Fruit> fruitComparer = whatever;
IComparable<Apples> 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.

like image 117
Eric Lippert Avatar answered Sep 20 '22 06:09

Eric Lippert


If you want to inherit from a generic interface, see phoog's answer. If you are talking about trying to implement an interface co-variantly, that leads to my discussion below.

Assume:

internal interface IAnInterface { }

public class SomeSubClass : IAnInterface { }

public class AnotherSubClass : IAnInterface { }

public GreatClass : IGreatInterface { ... }

The problem with trying to implement the interface with a more derived (co-variant) argument is there's no guarante when this is called through an interface that an IAnInterface passed in will be a SomeSubClass instance. This is why it's not allowed directly.

IGreatInterface x = new GreatClass();

x.aMethodBeta(new AnotherSubClass());

IF You could do covariance, this would fail because you would be expecting a SomeSubClass but would get a AnotherSubClass.

What you could do is to do explicit interface implementation:

class GreatInterface : IGreatInterface
{
    // explicitly implement aMethodBeta() when called from interface reference
    object IGreatInterface.aMethodBeta(IAnInterface parameter)
    {
        // do whatever you'd do on IAnInterface itself...
        var newParam = parameter as SomeSubClass;

        if (newParam != null)
        {
            aMethodBeta(newParam);
        }

        // otherwise do some other action...
    }

    // This version is visible from the class reference itself and has the 
    // sub-class parameter
    public object aMethodBeta(SomeSubClass parameter)
    {
        // do whatever
    }
}

Thus, if you did this, your interface supports the generic, the class has a more specific method, but still supports the interface. The main difference is you'd need to handle the case where an unexpected implementation of IAnInterface is passed in.

UPDATE: it sounds like you want something like this:

public interface ISomeInterface
{
    void SomeMethod<A>(A someArgument);
}

public class SomeClass : ISomeInterface
{
    public void SomeMethod<TA>(TA someArgument) where TA : SomeClass
    {

    }
}

This is not allowed, when you implement a generic method from an interface, the constraints must match.

like image 21
James Michael Hare Avatar answered Sep 19 '22 06:09

James Michael Hare