Covariance And Contravariance In C#

Covariance And Contravariance In C# Average ratng: 3,8/5 1415 votes

C’s classical OOP system supports “covariant return types,” but it does not support “contravariant parameter types.” The problem in the authors 'Animal doctor' example is that the universe of all possible animal types is practically unbounded, or 'open'. Covariance and contravariance are fully supported in both C and C for implicit conversions involving the const qualifier. Here’s covariance. (Remember that in C, when we want to produce something, we use an out-parameter — we “pass by pointer.”). C# the language, right from the very beginning is always supported covariance and contravariance but in most simple scenario, now in C# 4.0 we have full support of covariance and contravariance in all circumstances, generic interface and generic delegates, other C# types already support from first version of C#.

Contravariance

Today I’d like to try to explain covariance and contravariance, and the many places wesee that notion pop up in C++.

Covariance and contravariance, by example

Consider the following veterinary analogy.We have the notion of an animal. All animals can make noise.Cats are animals. Horses are animals that you can ride.

Crucially, it is part of the static type system that you can’tride cats. It’s not just that you try and it throws an exception, orsomething; cats literally don’t have a ride method. Okay?

Now we add a producer of animals; let’s say, an animal breeder.(I was going to say “a mama animal,” but then we’d have to decidewhether to express that a MamaCat was both a MamaAnimal and a Cat,and that way lies madness.)

Notice that a cat breeder IS-AN animal breeder. If you say to me,“Quick, I need the phone number of someone who can produce me an animal!”and I give you the number of a cat breeder, you will be happy.(That’s logic!)

Because a cat IS-AN animal, any breeder that produces cats by definitionIS-A breeder that produces animals. We say that the relationship betweenAnimal and Cat is covariant with the relationship betweenAnimalBreeder and CatBreeder.

Notice that a CatBreeder only ever produces Cats, and aHorseBreeder only ever produces Horses. Conveniently, C++ permits usto encode that extra snippet of information into our source code.

This is a feature known as “covariant return types.”

Now consider what happens if you already have a Cat, and you come tome and say, “Quick, I need the phone number of someone who can treat mysick cat!” I’d try to find you an animal doctor.

(Footnote: My lovely wife, the namesake of DoctorKeat, would also have atreat(Dog *) method at least. But for the purposes of this example,we’ll stick with cats.)

According to the static type system, Dr. Keat treats only cats, andDr. Hackenbush treats onlyhorses. Whereas, of course, Dr. Dolittlecan treat anything!

What does this mean for our class hierarchy? Well, consider Horse.A horse can do anything a generic animal can do, plus more (namely, be ridden),so we made Horse inherit from Animal. DoctorDolittle can do anythingDoctorHackenbush can do, plus more, so logically DoctorDolittle should inheritfrom DoctorHackenbush.

Taken literally, this would lead to a bit of a mess, because DoctorDolittlewould have to multiply inherit from DoctorHackenbushandDoctorKeatand a bunch more animal-doctor classes that we don’t even know about yet.Which is crazy. So let’s back off and just consider Dolittle and Hackenbushin isolation.

This hierarchy is logically correct. If you come to me and say,“I need the number of someone to treat my sick horse!” then you will behappy with any HorseDoctor; but if you need a horse doctor to treat yoursick cat, you’ll need a more capable horse doctor. If it helps, considerthat an AnimalDoctor like Dr. Dolittle is also a CatAndHorseDoctor, whichsounds more like a proper subclass of HorseDoctor.

We say that the relationship between Animal and Horse is contravariant withthe relationship between AnimalDoctor and HorseDoctor: an AnimalDoctorIS-A HorseDoctor precisely because a Horse IS-AN Animal.

Perhaps inconveniently, C++ does not permit us to write the function markedhmm... above. C++’s classical OOP system supports “covariant return types,” but itdoes not support “contravariant parameter types.”

This concludes our explanation of covariance and contravariance in the classicalOOP system. Now let’s look at other places the notion shows up in C++.

In std::function

(Thanks to Michał Dominiak for givingthis example.)

The C++ core language may not support contravariant parameter types, but thestandard library does, in the form of std::function and its many implicitconversions. Example:

We see that dolittle is an acceptable AnimalDoctor and thus also a HorseDoctor;whereas hackenbush is nothing more than a HorseDoctor.

Similarly, std::function supports covariant return types. Example:

If you say to me, “Quick, I need the phone number of someone who can produce me an animal!”and I give you the number of Dr. Moreau, you will be happy.(That’s logic!) But if you need specifically a horse, I should send you to a more capablebreeder, who is not only able to produce you an animal but also to give you compile-timeassurance that it is actually a horse. I’d better not send you to Moreau.With Moreau, you never know what you’re going to get.

In const-correctness

Covariance and contravariance are fully supported in both C and C++for implicit conversions involving the const qualifier.Here’s covariance.

(Remember that in C, when we want to produce something, we usean out-parameter — we “pass by pointer.”)

This snippet is logically correct! A horse is a more capable animal.In our classical OOP example, a horse was an animal you could ride().In this example, a horse is an animal whose target you can ++.(That is, a mutable int is a more capable const int. Rust got it right!)

Now you come to me and say, “Quick, I need the phone number of someonewho can produce me a horse! It’s a gift for my daughter.”

I can send your empty gift box over to Ben Ishak, whom I trust tofill it with a horse. I can’t send your box to Moreau; he’ll probablyfill it with some other kind of animal. With Moreau, you never know whatyou’re going to get.

(Incidentally, it is super important that the compiler does enforce thisrule for us. If moreau(&giftbox) were permitted to compile,then on Christmas morning your daughter would probably try to++*giftbox and end up riding that tiger by mistake.)

That’s covariance: the relationship between int const * and int *is covariant with the relationship between void(int const **)and void(int **). Now for contravariance:

If you come to me asking for someone to treat your cat (an Animalthat is not a Horse), I can send you to Dolittle but not to Hackenbush:

That’s contravariance: the relationship between int const * and int *is contravariant with the relationship between void(int const *)and void(int *).

In non-type template parameters (C++17)

In C++17, non-type template parameters (NTTPs) demonstrate contravariance:

Now you come and say to me, “Quick, I need someone who can treat my sick cat!”I can send you to Dr. Dolittle, but not to Dr. Hackenbush:

(GCC, Clang, and ICC agree on this point.MSVC believes it’d be fine to send you to Hackenbush.)

For the covariant case, we can use our “gift box” metaphor:

In this case, not a single compiler detects the problem withmoreau<your_giftbox>: everyone freely instantiates moreau and lets Moreau place a &tigerinside the Giftbox, which results in a hard error.

In non-type template parameters with alias templates

By the way, we can replace dolittle, hackenbush, and your_giftbox with alias templates;it doesn’t change any compiler’s behavior.(MSVC still incorrectly accepts you<hackenbush>;everyone still incorrectly accepts moreau<your_giftbox>.)

In this case, every compiler does give a hard error at the point of instantiation of Giftbox<&tiger>,which is refreshing — but nobody detects that your_giftbox should never have been given to moreauin the first place.

In constrained template type parameters (C++2a)

The C++2a Working Draft permits template type parameters to be constrained,and even gives the programmer a shorthand syntax which (unfortunately? fortunately?)makes the following example look almost identical to the NTTP example above.

Now you come and say to me, “Quick, I need someone who can treat my sick cat!”I can send you to Dr. Dolittle, but not to Dr. Hackenbush:

(GCC and Clang agree on this point.)

For the covariant case, we can use our “gift box” metaphor:

GCC and Clang agree that ben_ishak<your_giftbox>is acceptable and moreau<your_giftbox> is unacceptable.

In constrained template type parameters with alias templates

By the way, we can replace dolittle, hackenbush, and your_giftbox with alias templates;it doesn’t change any compiler’ behavior.

In variadic template parameters

A variadic parameter list (that could have any number of parameters) is like anAnimal; a parameter list constrained to take only two parameters is like a Horse.(A horse IS-AN animal, but not all animals are horses.)

Now you come and say to me, “Quick, I need someone who can treat my sick cat!”I should be able to send you to Dr. Dolittle, but not to Dr. Hackenbush:

(Here, Clang falters by incorrectly rejectingyou<dolittle>. GCC, ICC and MSVC accept it.Everybody correctly rejects you<hackenbush>.)

For the covariant case, we can use our “gift box” metaphor:

In this case, not a single compiler detects the problem withmoreau<your_giftbox>: everyone freely gives your_giftbox to moreau and lets Moreau tryto place a tiger inside it, which results in a hard error.

In variadic template parameters with alias templates

You might think this shouldn’t be different (and you’d be right that it shouldn’t), butin fact we do see a change in MSVC’s behavior…

With hackenbush expressed as an alias template instead of a class template,MSVC incorrectly acceptsyou<hackenbush>!Clang continues to incorrectly reject you<dolittle>.And nobody notices Moreau taking the giftbox until it’s too late.

More?

What are some more places that covariance and contravariance show up in C++?Think of other ways to smuggle “producers” (like moreau and ben_ishak) and“consumers” (like hackenbush and dolittle) into the language… and if you thinkof a good one, tell me about it!

The “implementation variance” on some of these variadic-template examples isthe subject of CWG1430.

> C++’s classical OOP system supports “covariant return types,” but it does not support “contravariant parameter types.”

Covariance And Contravariance In C# Youtube

The problem in the authors 'Animal doctor' example is that the universe of all possible animal types is practically unbounded, or 'open'.

What you want here are 'open multimethods': something Bjarne Stroustrup has wanted in C++ for a long time[0] but have been implemented in library form[1].

Of course, the thing to remember is that, as soon as you go to an open multiple dispatch arrangement, you will lose much of the static safety guarantees that the type-system affords us. There will always be runtime/dynamic type checking, and a runtime failure case.

Covariance And Contravariance In Delegates C#

If you don't want an open system, then some combination of templates for static polymorphism and strategic type-erasure will get the job done. e.g. use boost::variant and multivisitation [2], and the compiler will instantiate all O(n^2) methods for you.

Covariance And Contravariance C# Classes

[0] http://www.stroustrup.com/multimethods.pdf (2007)

[1] https://github.com/jll63/yomm2/blob/master/examples/asteroid...

[2] https://www.boost.org/doc/libs/1_69_0/doc/html/variant/tutor...