The benefits of contravariance in the IComparer and IEqualityComparer interfaces

On the msdn page on contravariance, I find a rather interesting example that shows the "benefits of contravariance in IComparer"

First they use the rather odd base and derived classes:

public class Person { public string FirstName { get; set; } public string LastName { get; set; } } public class Employee : Person { } 

I can already say that his bad example does not cause the class to ever just inherit the base class without adding at least a little something of its own.

Then they create a simple IEqualityComparer class

 class PersonComparer : IEqualityComparer<Person> { public bool Equals(Person x, Person y) { .. } public int GetHashCode(Person person) { .. } } 

The following is an example.

 List<Employee> employees = new List<Employee> { new Employee() {FirstName = "Michael", LastName = "Alexander"}, new Employee() {FirstName = "Jeff", LastName = "Price"} }; IEnumerable<Employee> noduplicates = employees.Distinct<Employee>(new PersonComparer()); 

Now my question is - first of all, in this case Employee is an unnecessary class, its true that it can use PersonComparer for this situation, because it is really just a human class!

In the real world, however, Employee will have at least one new field, say a JobTitle . Given that it is pretty clear that when we want Distint employees to be needed, we need to consider this JobTitle field for comparison, and it’s pretty clear that a Contravariant Comparer, such as a face mapper, is not suitable for this job because it doesn’t can know no new members The employee identified.

Now, of course, any language can even be very strange, even if it is illogical for any situation, but in this case I think that too often it will not be useful for default behavior. In fact, it seems to me that we are a little violating type safety, when a method expects Employee comparisons, we can actually put a person or even an object comparator, and it compiles without problems. While it is difficult to imagine that our default scenario will refer to the Employee as an object ... or to the main Person.

So is this really good default contravariance for these interfaces?

EDIT: I understand what contravariance and covariance are. I ask why these interface comparisons have been modified to be contravariant by default.

+4
source share
3 answers

This question comes more like ranting, but allows you to take a moment and talk about the compart.

IEqualityComparer<T> is useful when you need to override any default IEqualityComparer<T> that is available for comparison. It can use its own equality logic (overriding Equals and GetHashCode), it can use default reference equality, no matter. The fact is that you do not want everything to be the default. IEqualityComparer<T> allows you to specify exactly what you want to use for equality. And this allows you to identify as many different ways as you need to solve the many different problems that you may have.

One of these many problems can only be resolved with a resolver that already exists for a less derived type. With everything that happens here, you have the opportunity to supply a comparator that solves the problem you need to solve. You can use a more general comparator with a more derived collection.

In this task, you say, “OK, to compare only the basic properties, but it’s not good for me to put a smaller derivative (or native) object in the collection.”

+2
source

The definition of contravariant is as follows. A map F from type mappings to types T to F<T> is contravariant in T if each time U and V are types, so that every object of type U can be associated with a variable of type V , every object of type F<V> can be a variable of type F<U> is assigned ( F overrides assignment compatibility).

In particular, if T -> IComparer<T> , then note that a variable of type IComparer<Derived> can receive an object that implements IComparer<Base> . This is contravariance.

The reason we say that IComparer<T> is contravariant in T is because you can say

 class SomeAnimalComparer : IComparer<Animal> { // details elided } 

and then:

 IComparer<Cat> catComparer = new SomeAnimalComparer(); 

Edit: You say:

I understand what contravariance and covariance are. I ask why these interface comparisons have been modified to be contravariant by default.

Changed? I mean that IComparer<T> “natural” contra IComparer<T> . IComparer<T> definition:

  public interface IComparer<T> { int Compare(T x, T y); } 

Note that T displayed only at the "in" position in this interface. That is, there are no methods that return instances of T Any such interface is “naturally” contravariant in T

Given this, what is your reason not to make it contravariant? If you have an object that knows how to compare instances of U , and V is an assignment compatible with U , why don't you think of this object as knowing how to compare instances of V ? This is what allows contravariance.

Before contravariance you will have to wrap:

  class ContravarianceWrapperForIComparer<U, V> : IComparer<V> where V : U { private readonly IComparer<U> comparer; public ContravarianceWrapperForIComparer(IComparer<U> comparer) { this.comparer = comparer; } public int Compare(V x, V y) { return this.comparer.Compare(x, y); } } 

And then you could say

 class SomeUComparer : IComparer<U> { // details elided } IComparer<U> someUComparer = new SomeUComparer(); IComparer<V> vComparer = new ContravarianceWrapperForIComparer<U, V>(someUComparer); 

Contravariance allows you to skip these spells and just say

 IComparer<V> vComparer = someUComparer; 

Of course, it was only higher when V : U With contravariance, you can do this every time U matches the destination of V

+4
source

The employee is a person. Because the comparator requires a superclass of Person, you guarantee that these elements can be compared as long as they extend Person. The idea is that Man must always be comparable with the Personal. A great example of why this is so is:

If we have Person.ssn, this should be compared in the .equals () method. Because we compare ssn and we guarantee that ssn is unique to each person; it doesn’t matter that Employee is a Manager or Fry Cook, as we have ensured that they are the same.

Now, if you can have several people with the same SSN and different attributes of Employee; then you should consider not making the person a contravariant type and making Employee as valid as possible.

Contravariance helps to program the interface and does not know every possible implementation of the interface. This, of course, provides better extensibility and the creation of new instances of the interface to extend the functionality of your program.

The contravariant array types also help us create nested classes that extend the interface and pass them back. For example, if we create an array of Employee, and we do not necessarily want to expose Employee to res of the world; we can send an array of people; and due to contravariance, we can actually send an Employee array as an entity array.

+1
source

All Articles