Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
142 views
in Technique[技术] by (71.8m points)

java - What are the kinds of covariance in C#? (Or, covariance: by example)

Covariance is (roughly) the ability to mirror inheritance of "simple" types in complex types that use them.
E.g. We can always treat an instance of Cat as an instance of Animal. A ComplexType<Cat> may be treated as a ComplexType<Animal>, if ComplexType is covariant.

I'm wondering: what are the "types" of covariance, and how do they relate to C# (are they supported?)
Code examples would be helpful.

For instance, one type is return type covariance, supported by Java, but not C#.

I'm hoping someone with functional programming chops can chime in, too!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Here's what I can think of:

Update

After reading the constructive comments and the ton of articles pointed (and written) by Eric Lippert, I improved the answer:

  • Updated the broken-ness of array covariance
  • Added "pure" delegate variance
  • Added more examples from the BCL
  • Added links to articles that explain the concepts in-depth.
  • Added a whole new section on higher-order function parameter covariance.

Return type covariance:

Available in Java (>= 5)[1] and C++[2], not supported in C# (Eric Lippert explains why not and what you can do about it):

class B {
    B Clone();
}

class D: B {
    D Clone();
}

Interface covariance[3] - supported in C#

The BCL defines the generic IEnumerable interface to be covariant:

IEnumerable<out T> {...}

Thus the following example is valid:

class Animal {}
class Cat : Animal {}

IEnumerable<Cat> cats = ...
IEnumerable<Animal> animals = cats;

Note that an IEnumerable is by definition "read-only" - you can't add elements to it.
Contrast that to the definition of IList<T> which can be modified e.g. using .Add():

public interface IEnumerable<out T> : ...  //covariant - notice the 'out' keyword
public interface IList<T> : ...            //invariant

Delegate covariance by means of method groups [4] - supported in C#

class Animal {}
class Cat : Animal {}

class Prog {
    public delegate Animal AnimalHandler();

    public static Animal GetAnimal(){...}
    public static Cat GetCat(){...}

    AnimalHandler animalHandler = GetAnimal;
    AnimalHandler catHandler = GetCat;        //covariance

}

"Pure" delegate covariance[5 - pre-variance-release article] - supported in C#

The BCL definition of a delegate that takes no parameters and returns something is covariant:

public delegate TResult Func<out TResult>()

This allows the following:

Func<Cat> getCat = () => new Cat();
Func<Animal> getAnimal = getCat; 

Array covariance - supported in C#, in a broken way[6] [7]

string[] strArray = new[] {"aa", "bb"};

object[] objArray = strArray;    //covariance: so far, so good
//objArray really is an "alias" for strArray (or a pointer, if you wish)


//i can haz cat?
object cat == new Cat();         //a real cat would object to being... objectified.

//now assign it
objArray[1] = cat                //crash, boom, bang
                                 //throws ArrayTypeMismatchException

And finally - the surprising and somewhat mind-bending
Delegate parameter covariance (yes, that's co-variance) - for higher-order functions.[8]

The BCL definition of the delegate that takes one parameter and returns nothing is contravariant:

public delegate void Action<in T>(T obj)

Bear with me. Let's define a circus animal trainer - he can be told how to train an animal (by giving him an Action that works with that animal).

delegate void Trainer<out T>(Action<T> trainingAction);

We have the trainer definition, let's get a trainer and put him to work.

Trainer<Cat> catTrainer = (catAction) => catAction(new Cat());

Trainer<Animal> animalTrainer = catTrainer;  
// covariant: Animal > Cat => Trainer<Animal> > Trainer<Cat> 

//define a default training method
Action<Animal> trainAnimal = (animal) => 
   { 
   Console.WriteLine("Training " + animal.GetType().Name + " to ignore you... done!"); 
   };

//work it!
animalTrainer(trainAnimal);

The output proves that this works:

Training Cat to ignore you... done!

In order to understand this, a joke is in order.

A linguistics professor was lecturing to his class one day.
"In English," he said, "a double negative forms a positive.
However," he pointed out, "there is no language wherein a double positive can form a negative."

A voice from the back of the room piped up, "Yeah, right."

What's that got to do with covariance?!

Let me attempt a back-of-the-napkin demonstration.

An Action<T> is contravariant, i.e. it "flips" the types' relationship:

A < B => Action<A> > Action<B> (1)

Change A and B above with Action<A> and Action<B> and get:

Action<A> < Action<B> => Action<Action<A>> > Action<Action<B>>  

or (flip both relationships)

Action<A> > Action<B> => Action<Action<A>> < Action<Action<B>> (2)     

Put (1) and (2) together and we have:

,-------------(1)--------------.
 A < B => Action<A> > Action<B> => Action<Action<A>> < Action<Action<B>> (4)
         `-------------------------------(2)----------------------------'

But our Trainer<T> delegate is effectively an Action<Action<T>>:

Trainer<T> == Action<Action<T>> (3)

So we can rewrite (4) as:

A < B => ... => Trainer<A> < Trainer<B> 

- which, by definition, means Trainer is covariant.

In short, applying Action twice we get contra-contra-variance, i.e. the relationship between types is flipped twice (see (4) ), so we're back to covariance.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...