Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the concept of "Covariance" and "Contravariance" are applicable while implementing the methods of an interface?

The use case is some what like this:

public class SomeClass : ICloneable
{
    // Some Code

    // Implementing interface method
    public object Clone()
    {
        // Some Clonning Code
    }
}

Now my question is Why is it not possible to use "SomeClass(As it is derived from object)" as a return type of Clone() method if we consider the Funda's of Covariance and Contravariance?

Can somebody explain me the reason behind this implementation of Microsoft ????

like image 639
Amit Avatar asked Mar 23 '10 17:03

Amit


2 Answers

Let me rephrase the question:

Languages such as C++ allow an overriding method to have a more specific return type than the overridden method. For example, if we have types

abstract class Enclosure {}
class Aquarium : Enclosure {}
abstract class Animal 
{
    public virtual Enclosure GetEnclosure();
}

then this is not legal in C# but the equivalent code would be legal in C++:

class Fish : Animal
{
    public override Aquarium GetEnclosure() { ... 

What is this feature of C++ called?

The feature is called "return type covariance". (As another answer points out, it would also be possible to support "formal parameter type contravariance", though C++ does not.)

Why is it not supported in C#?

As I've pointed out many times, we don't have to provide a reason why a feature is not supported; the default state of all features is "not supported". It's only when huge amounts of time and effort are put into making an implementation that a feature becomes supported. Rather, features that are implemented must have reasons for them, and darn good reasons at that considering how much it costs to make them.

That said, there are two big "points against" this feature that are the primary things preventing it from getting done.

  1. The CLR does not support it. In order to make this work we'd basically have to implement the exactly matching method and then make a helper method that calls it. It's doable but it gets to be messy.

  2. Anders thinks it is not a very good language feature. Anders is the Chief Architect and if he thinks it is a bad feature, odds are good its not going to get done. (Now, mind you, we thought that named and optional parameters was not worth the cost either, but that did eventually get done. Sometimes it becomes clear that you do have to grit your teeth and implement a feature that you don't really like the aesthetics of in order to satisfy a real-world demand.)

In short, there are certainly times when it would be useful, and this is a frequently requested feature. However, it's unlikely that we're going to do it. The benefit of the feature does not pay for its costs; it considerably complicates the semantic analysis of methods, and we have no really easy way to implement it.

like image 188
Eric Lippert Avatar answered Oct 20 '22 19:10

Eric Lippert


A non-broken implementation of interface-implementation variance would have to be covariant in the return type and contravariant in the argument types.

For example:

public interface IFoo
{
    object Flurp(Array array);
}

public class GoodFoo : IFoo
{
    public int Flurp(Array array) { ... }
}

public class NiceFoo : IFoo
{
    public object Flurp(IEnumerable enumerable) { ... }
}

Both are legal under the "new" rules, right? But what about this:

public class QuestionableFoo : IFoo
{
    public double Flurp(Array array) { ... }
    public object Flurp(IEnumerable enumerable) { ... }
}

Kind of hard to tell which implicit implementation is better here. The first one is an exact match for the argument type, but not the return type. The second is an exact match for the return type, but not the argument type. I'm leaning toward the first, because whoever uses the IFoo interface can only ever give it an Array, but it's still not entirely clear.

And this isn't the worst, by far. What if we do this instead:

public class EvilFoo : IFoo
{
    public object Flurp(ICollection collection) { ... }
    public object Flurp(ICloneable cloneable) { ... }
}

Which one wins the prize? It's a perfectly valid overload, but ICollection and ICloneable have nothing to do with each other and Array implements both of them. I can't see an obvious solution here.

It only gets worse if we start adding overloads to the interface itself:

public interface ISuck
{
    Stream Munge(ArrayList list);
    Stream Munge(Hashtable ht);
    string Munge(NameValueCollection nvc);
    object Munge(IEnumerable enumerable);
}

public class HateHateHate : ISuck
{
    public FileStream Munge(ICollection collection);
    public NetworkStream Munge(IEnumerable enumerable);
    public MemoryStream Munge(Hashtable ht);
    public Stream Munge(ICloneable cloneable);
    public object Munge(object o);
    public Stream Munge(IDictionary dic);
}

Good luck trying to unravel this mystery without going insane.

Of course, all of this is moot if you assert that interface implementations should only support return-type variance and not argument-type variance. But almost everyone would consider such a half-implementation to be completely broken and start spamming bug reports, so I don't think that the C# team is going to do it.

I don't know if this is the official reason why it's not supported in C# today, but it should serve as a good example of the kind of "write-only" code that it could lead to, and part of the C# team's design philosophy is to try to prevent developers from writing awful code.

like image 6
Aaronaught Avatar answered Oct 20 '22 17:10

Aaronaught