Doing Distinct () using IEqualityComparer base class and still returning child class type?

I have several classes that are derived from the BaseClass class, where BaseClass has only the Id property.

Now I need to make a difference in the sets of some of these objects. I have the following code over and over for each of the child classes:

 public class PositionComparer : IEqualityComparer<Position> { public bool Equals(Position x, Position y) { return (x.Id == y.Id); } public int GetHashCode(Position obj) { return obj.Id.GetHashCode(); } } 

Given that the logic is based only on Id , I wanted to create one comparator to reduce duplication:

 public class BaseClassComparer : IEqualityComparer<BaseClass> { public bool Equals(BaseClass x, BaseClass y) { return (x.Id == y.Id); } public int GetHashCode(BaseClass obj) { return obj.Id.GetHashCode(); } } 

But this does not look like compilation:

  IEnumerable<Position> positions = GetAllPositions(); positions = allPositions.Distinct(new BaseClassComparer()) 

... because he says that he cannot convert from BaseClass to Position . Why does the comparator force the value of this Distinct() call?

+6
source share
5 answers

If you look at the Distinct definition, only one generic type parameter is used (and not one TCollection used for input and output collections and one TComparison for comparison). This means that your BaseClassComparer limits the type of the result to the base class, and conversion at assignment is not possible.

Perhaps you can create a GenericComparer with a common parameter that is limited to at least the base class, which can bring you closer to what you are trying to do. It will look like

 public class GenericComparer<T> : IEqualityComparer<T> where T : BaseClass { public bool Equals(T x, T y) { return x.Id == y.Id; } public int GetHashCode(T obj) { return obj.Id.GetHashCode(); } } 

Since you need an instance, not just a method call, you cannot allow the generic type to be inferred by the compiler ( see discussion ), but you must therefore do this when creating the instance:

 IEnumerable<Position> positions; positions = allPositions.Distinct(new GenericComparer<Position>()); 

Eric's answer explains the root cause of the whole problem (in terms of covariance and contravariance).

+5
source

UPDATE: This question was the topic of my blog in July 2013 . Thanks for the great question!


You have found an unsuccessful edge case in the output method of a typical method. We have:

 Distinct<X>(IEnumerable<X>, IEqualityComparer<X>) 

where the interfaces are:

 IEnumerable<out T> -- covariant 

and

 IEqualityComparer<in T> -- contravariant 

When we conclude from allPositions to IEnumerable<X> , we say: " IEnumerable<T> covariant in T, so we can accept Position or any larger type (the base type is" larger "than the derived type, there are more animals in the world, than giraffes.)

When we conclude from the comparator, we say: " IEqualityComparer<T> is contravariant in T, so we can accept a BaseClass or any smaller type."

So what happens when the time comes to infer a type argument? We have two candidates: Position and BaseClass . Both satisfy the specified boundaries . Position satisfies the first estimate because it is identical to the first estimate and satisfies the second estimate because it is less than the second. BaseClass satisfies the first BaseClass because it is larger than the first grade and matches the second grade.

We have two winners. We need a switch. What are we doing in this situation?

This was the point of some debate, and there are arguments on three sides: choose more specific types, choose more general types, or don't let the type enter. I will not rephrase the whole argument, but suffice it to say that the “choose a more general” side won the day.

(To make matters worse, there is a typo in the specification saying that “choosing a more specific one” is the right thing! This was the result of an editing error during a design process that never fixed. The compiler implements “choosing a more general one.” I recalled Mads error and hopefully this will be fixed in the C # 5 spec.)

So you go. In this situation, the type inference selects a more general type and indicates that the call means Distinct<BaseClass> . Type inference never takes into account the type of the return value, and of course it does not accept what is assigned to this expression, so the fact that it selects a type that is incompatible with the assigned variable is not a business.

My advice is to explicitly specify a type argument in this case.

+8
source

Imagine that you had:

 var positions = allPositions.Distinct(new BaseClassComparer()); 

What would you expect from the positions type? Because the compiler infers from the argument given by Distinct that implements IEqualityComparer<BaseClass> , the type of the expression is IEnumerable<BaseClass> .

This type cannot be automatically converted to IEnumerable<Position> , so the compiler throws an error.

+1
source

Since IEqualityComparer<T> is contravariant in type T , you can use the base class mapping to excellent if you specify the general Distinct parameter:

 IEnumerable<Position> distinct = positions.Distinct<Position>(new BaseClassComparer()); 

If you do not specify this, the compiler will display the type T as BaseClass , since BaseClassComparer implements IEqualityComparer<BaseClass> .

0
source

You need small changes in your code. Working example:

 public class BaseClass { public int Id{get;set;} } public class Position : BaseClass { public string Name {get;set;} } public class Comaprer<T> : IEqualityComparer<T> where T:BaseClass { public bool Equals(T x, T y) { return (x.Id == y.Id); } public int GetHashCode(T obj) { return obj.Id.GetHashCode(); } } class Program { static void Main(string[] args) { List<Position> all = new List<Position> { new Position { Id = 1, Name = "name 1" }, new Position { Id = 2, Name = "name 2" }, new Position { Id = 1, Name = "also 1" } }; var distinct = all.Distinct(new Comaprer<Position>()); foreach(var d in distinct) { Console.WriteLine(d.Name); } Console.ReadKey(); } } 
0
source

All Articles