Is it possible with C# to pass a lambda expression as an IComparer argument in a method call?
eg something like
var x = someIEnumerable.OrderBy(aClass e => e.someProperty,
(aClass x, aClass y) =>
x.someProperty > y.SomeProperty ? 1 : x.someProperty < y.SomeProperty ? -1 : 0);
I can't quite get this to compile so I'm guessing not, but it seems such an obvious synergy between lambdas and anonymous delegates that I feel I must be doing something foolishly wrong.
TIA
If you're on .NET 4.5, you can use the static method Comparer<aClass>.Create.
Documentation: Comparer<T>.Create Method .
Example:
var x = someIEnumerable.OrderBy(e => e.someProperty,
Comparer<aClass>.Create((x, y) => x.someProperty > y.SomeProperty ? 1 : x.someProperty < y.SomeProperty ? -1 : 0)
);
As Jeppe points out, if you're on .NET 4.5, you can use the static method Comparer<T>.Create.
If not, this is an implementation that should be equivalent:
public class FunctionalComparer<T> : IComparer<T>
{
private Func<T, T, int> comparer;
public FunctionalComparer(Func<T, T, int> comparer)
{
this.comparer = comparer;
}
public static IComparer<T> Create(Func<T, T, int> comparer)
{
return new FunctionalComparer<T>(comparer);
}
public int Compare(T x, T y)
{
return comparer(x, y);
}
}
If you consistently want to compare projected keys (such as a single property), you can define a class that encapsulates all the key comparison logic for you, including null checks, key extraction on both objects, and key comparison using the specified or default inner comparer:
public class KeyComparer<TSource, TKey> : Comparer<TSource>
{
private readonly Func<TSource, TKey> _keySelector;
private readonly IComparer<TKey> _innerComparer;
public KeyComparer(
Func<TSource, TKey> keySelector,
IComparer<TKey> innerComparer = null)
{
_keySelector = keySelector;
_innerComparer = innerComparer ?? Comparer<TKey>.Default;
}
public override int Compare(TSource x, TSource y)
{
if (object.ReferenceEquals(x, y))
return 0;
if (x == null)
return -1;
if (y == null)
return 1;
TKey xKey = _keySelector(x);
TKey yKey = _keySelector(y);
return _innerComparer.Compare(xKey, yKey);
}
}
For convenience, a factory method:
public static class KeyComparer
{
public static KeyComparer<TSource, TKey> Create<TSource, TKey>(
Func<TSource, TKey> keySelector,
IComparer<TKey> innerComparer = null)
{
return new KeyComparer<TSource, TKey>(keySelector, innerComparer);
}
}
You could then use this like so:
var sortedSet = new SortedSet<MyClass>(KeyComparer.Create((MyClass o) => o.MyProperty));
You can refer to my blog post for an expanded discussion of this implementation.
Related
I have a list of movies, and I need to merge them with another list and duplicate.
I am using Jon Skeet's DistinctBy(m => m.SomeUniqueMovieProperty) to achieve this and it works OK. Except, we soon discovered that there would be cases where 10-20% of the movies (in either list) don't have this property filled out, causing DistinctBy to collapse them into 1 lucky movie.
This is a problem, we want to keep all those movies that don't have a value for this property. Initially I thought of extracting these movies from each collection, duplicating, then merging them again, is there a shorter solution to this problem?
Concatenate the results of DistinctBy() with the results of Where([null or empty]).
var nullMovies = allMovies.Where(m=>string.IsNullOrEmpty(m.SomeUniqueMovieProperty));
var distinctNonNullMovies = allMovies.Where(m => !string.IsNullOrEmpty(m.SomeUniqueMovieProperty)).DistinctBy(m => m.SomeUniqueMovieProperty);
var result = nullMovies.Concat(distinctNonNullMovies);
Assuming m's Equals/GetHashCode isn't overridden, if m.SomeUniqueMovieProperty is null and you don't have any other unique key, you can use m itself as the unique key.
DistinctBy(m => (object) m.SomeUniqueMovieProperty ?? m)
If you want to include all of the nulls you need to replace the null property with something that is unique when it is null. Assuming the property is a string a Guid will do nicely for this job.
.DistinctBy(m => m.SomeUniqueMovieProperty ?? Guid.NewGuid().ToString())
Any time it hits a property with a null value it will be filled in with a random new guid value.
If you want to also have empty titles not get removed change the query to
.DistinctBy(m => String.IsNullOrEmpty(m.SomeUniqueMovieProperty) ? Guid.NewGuid().ToString() : m.SomeUniqueMovieProperty)
Another option is make your own DistinctBy that behaves the way you want. This is a tweaked version of the original source that only applies the filter if shouldApplyFilter returns true, comments are also removed for brevity.
static partial class MoreEnumerable
{
public static IEnumerable<TSource> ConditionalDistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, bool> shouldApplyFilter)
{
return source.ConditionalDistinctBy(keySelector, shouldApplyFilter, null);
}
public static IEnumerable<TSource> ConditionalDistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, bool> shouldApplyFilter, IEqualityComparer<TKey> comparer)
{
if (source == null) throw new ArgumentNullException("source");
if (keySelector == null) throw new ArgumentNullException("keySelector");
if (shouldApplyFilter == null) throw new ArgumentNullException("shouldApplyFilter");
return ConditionalDistinctByImpl(source, keySelector, shouldApplyFilter, comparer);
}
private static IEnumerable<TSource> ConditionalDistinctByImpl<TSource, TKey>(IEnumerable<TSource> source,
Func<TSource, TKey> keySelector, Func<TKey, bool> shouldApplyFilter, IEqualityComparer<TKey> comparer)
{
var knownKeys = new HashSet<TKey>(comparer);
foreach (var element in source)
{
var key = keySelector(element);
if (shouldApplyFilter(key) && knownKeys.Add(key))
{
yield return element;
}
}
}
}
It would be used like
.ConditionalDistinctBy(m => m.SomeUniqueMovieProperty, s => !String.IsNullOrEmpty(s));
Perhaps you could filter them on a composite distinct key, like as follows
movies.DistinctBy(m => String.Format({0}{1}{...},m.prop1,m.prop2,[]));
One last way, which is probably overkill, you can implement IEqualityComparer and put the logic in there if null is consider unique. DistinctBy has an overload for just this case.
public class MovieComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
if (x == null || y == null)
{
return false;
}
return x == y;
}
public int GetHashCode(string obj)
{
if (obj == null)
{
return 0;
}
return obj.GetHashCode();
}
}
I have a class (Patch) that I want to have sorted so I implemented IComparer.
However, it needs to be sorted depending on how the user wants it, e.g.:
- key1, key2, key3
- key1, key3, key2
For each key compare I have written a IComparer class, however, I was wondering how to implement its connection. i.e. when sorting I only can pass one IComparer instance.
Or should I make an IComparer class for each kind of full sorting, i.e. IComparerKey1Key2Key3, IComparerKey1Key3Key2 etc?
You could make a generic comparer that takes a delegate to select the key:
class ByKeyComparer<T, TKey> : IComparer<T>
{
private readonly Func<T, TKey> _keySelector;
private readonly IComparer<TKey> _keyComparer;
public ByKeyComparer(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
if (keySelector == null) throw new ArgumentNullException("keySelector");
_keySelector = keySelector;
_keyComparer = keyComparer ?? Comparer<TKey>.Default;
}
public int Compare(T x, T y)
{
return _keyComparer.Compare(_keySelector(x), _keySelector(y));
}
}
With a helper class to take advantage of type inference (so you don't need to specify the type of the key):
static class ByKeyComparer<T>
{
public static IComparer<T> Create<TKey>(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
return new ByKeyComparer<T, TKey>(keySelector, keyComparer);
}
}
You can use it like this:
var patchVersionComparer = ByKeyComparer<Patch>.Create(p => p.Version);
patches.Sort(patchVersionComparer);
If you need to combine several compare keys, you can create a comparer that uses other comparers:
class CompositeComparer<T> : IComparer<T>
{
private readonly IEnumerable<IComparer<T>> _comparers;
public CompositeComparer(IEnumerable<IComparer<T>> comparers)
{
if (comparers == null) throw new ArgumentNullException("comparers");
_comparers = comparers;
}
public CompositeComparer(params IComparer<T>[] comparers)
: this((IEnumerable<IComparer<T>>)comparers)
{
}
public int Compare(T x, T y)
{
foreach (var comparer in _comparers)
{
int result = comparer.Compare(x, y);
if (result != 0)
return result;
}
return 0;
}
}
Example usage:
var comparer = new CompositeComparer<Patch>(
ByKeyComparer<Patch>.Create(p => p.Key1),
ByKeyComparer<Patch>.Create(p => p.Key2),
ByKeyComparer<Patch>.Create(p => p.Key3));
patches.Sort(comparer);
EDIT: here's a more fluent API:
static class ByKeyComparer<T>
{
public static IComparer<T> CompareBy<TKey>(Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
return new ByKeyComparer<T, TKey>(keySelector, keyComparer);
}
}
static class ComparerExtensions
{
public static IComparer<T> ThenBy<T, TKey>(this IComparer<T> comparer, Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
var newComparer = ByKeyComparer<T>.CompareBy(keySelector, keyComparer);
var composite = comparer as CompositeComparer<T>;
if (composite != null)
return new CompositeComparer<T>(composite.Comparers.Concat(new[] { newComparer }));
return new CompositeComparer<T>(comparer, newComparer);
}
}
Example:
var comparer = ByKeyComparer<Patch>.CompareBy(p => p.Key1)
.ThenBy(p => p.Key2)
.ThenBy(p => p.Key3);
patches.Sort(comparer);
(obviously you might want to add *Descending versions of the CompareBy and ThenBy methods to allow ordering in descending order)
If you can use LINQ it'll be quite easy to sort classes like this.
Consider you have a List of Patch List<Patch> and you wanna sort it by key2, key1 and key4. What you do is:
List<Patch> patches = new List<Patch>();
patches = GetPatches().ToList().OrderBy(p=>p.Key2).ThenBy(p=>p.Key1).ThenBy(p=>p.Key4).ToList();
That's all. we love linq. :)
First ToList is not needed if function returns list itself.
You also can use LINQ Dynamic Query Library
or have a look at Dynamic LINQ OrderBy
I have a MongoDB database where I store all pictures and when I retrieve them I have stored some doubles, which ain't so good, but anyway I want to show only distinct elements.
#foreach (Foto f in fotos.Distinct(new IEqualityComparer<Foto> { )
But the Foto class has one property called smallurl and I want to show only distinct elements by this property. So how to write a custom IEqualityComparer.
var listOfUrls = fotos.Select(f => f.smallurl).Distinct();
EDIT to specifically answer your question
Practically copied from the MSDN documentation that you can find with a search for c# IEqualityComparer http://msdn.microsoft.com/en-us/library/ms132151.aspx
class FotoEqualityComparer : IEqualityComparer<Foto>
{
public bool Equals(Foto f1, Foto f2)
{
return f1.smallurl == f2.smallurl;
}
public int GetHashCode(Foto f)
{
return f.smallurl.GetHashCode();
}
}
#foreach (Foto f in fotos.Distinct(new FotoEqualityComparer() )
It's actually pretty easy. Simply provide a distinct-ness selector for your method like so:
public static IEnumerable<TSource> DistinctBy<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> keySelector)
{
Dictionary<TResult, TSource> seenItems = new Dictionary<TResult, TSource>();
foreach (var item in enumerable)
{
var key = keySelector(item);
if (!seenItems.ContainsKey(key))
{
seenItems.Add(key, item);
yield return item;
}
}
}
Alternatively, you can create another one to make a generic implementation fo the IEquality comparer:
public static IEnumerable<TSource> DistinctBy<TSource>(this IEnumerable<TSource> enumerable, Func<TSource, TSource, bool> equalitySelector, Func<TSource, int> hashCodeSelector)
{
return enumerable.Distinct(new GenericEqualitySelector<TSource>(equalitySelector, hashCodeSelector));
}
class GenericEqualitySelector<TSource> : IEqualityComparer<TSource>
{
public Func<TSource, TSource, bool> _equalityComparer = null;
public Func<TSource, int> _hashSelector = null;
public GenericEqualitySelector(Func<TSource, TSource, bool> selector, Func<TSource, int> hashSelector)
{
_equalityComparer = selector;
_hashSelector = hashSelector;
}
public bool Equals(TSource x, TSource y)
{
return _equalityComparer(x, y);
}
public int GetHashCode(TSource obj)
{
return _hashSelector(obj);
}
}
Create your own:
public class FotoEqualityComparer : IEqualityComparer<Foto>
{
public bool Equals(Foto x, Foto y)
{
return x.smallurl.Equals(y.smallurl);
}
public int GetHashCode(Foto foto)
{
return foto.smallurl.GetHashCode();
}
}
And use it like so:
fotos.Distinct(new FotoEqualityComparer());
EDIT:
There's no inline lambda overload of .Distinct() because when two objects compare equal they must have the same GetHashCode return value (or else the hash table used internally by Distinct will not function correctly).
But if you want it in one line, then you could also do grouping to achieve the same result:
fotos.GroupBy(f => f.smallurl).Select(g => g.First());
Modified from MSDN
public class MyEqualityComparer : IEqualityComparer<Foto>
{
public bool Equals(Foto x, Foto y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the foto's properties are equal.
return x.smallurl == y.smallurl ;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(Foto foto)
{
//Check whether the object is null
if (Object.ReferenceEquals(foto, null)) return 0;
//Get hash code for the foto.smallurl field if it is not null.
return foto.smallurl == null ? 0 : foto.smallurl.GetHashCode();
}
}
Much simpler code using GroupBy instead:
#foreach (Foto f in fotos.GroupBy(f => f.smallurl).Select(g => g.First()))
You should create your own EqulityComparer:
class FotoEqualityComparer : IEqualityComparer<Foto>
{
public bool Equals(Foto b1, Foto b2)
{
if (b1.smallurl == b2.smallurl)
return true;
else
return false;
}
public int GetHashCode(Foto bx)
{
int hCode = bx.smallurl ;
return hCode.GetHashCode();
}
}
I want to write a new override of Except extension method for IEnumerable which is able to take a comparer inline instead of using IEqualityComparer.
A, B are collections of a reference type..
Something like this:
A: [1, A], [2, A], [3, A]
B: [3, B], [4, B], [5, B]
C = A.Except(B, (a,b) => a.Id == b.Id);
C: [1, A], [2, A]
I wonder if you could help me with the code of the method.
public static class IEnumerableExntesion
{
public IEnumerable<T> Except<T>(this IEnumerable<T> source,
IEnumerable<T> second,
Func<T, T, bool> predicate)
{
}
}
I was thinking of:
return source.Where (s => !second.Any(p => p.Id == s.Id));
But actually I couldn't convert it to a generic solution using the passed predicate!
Any help!
Do you need to do the comparison using a predicate or can you use a projection instead and compare the projected values? If so then you could use some sort of ExceptBy method:
var c = a.ExceptBy(b, x => x.Id);
var r = p.ExceptBy(q, x => x.Name, StringComparer.OrdinalIgnoreCase);
// ...
public static class EnumerableExtensions
{
public static IEnumerable<TSource> ExceptBy<TSource, TKey>(
this IEnumerable<TSource> first, IEnumerable<TSource> second,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> keyComparer = null)
{
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (keySelector == null) throw new ArgumentNullException("keySelector");
return first.ExceptByIterator(second, keySelector, keyComparer);
}
private static IEnumerable<TSource> ExceptByIterator<TSource, TKey>(
this IEnumerable<TSource> first, IEnumerable<TSource> second,
Func<TSource, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
{
var keys = new HashSet<TKey>(second.Select(keySelector), keyComparer);
foreach (TSource item in first)
{
if (keys.Add(keySelector(item)))
yield return item;
}
}
}
This should work:
return source.Where(s => !second.Any(p => predicate(s, p))
return source.Where(s => !second.Any(p => predicate(p, s)));
I would instead use your passed Func<T,T,bool> to create a generic IEqualityComparer and pass it to regular Except:
public class PredicateEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _predicate;
public PredicateEqualityComparer(Func<T, T, bool> predicate)
{
_predicate = predicate;
}
public bool Equals(T x, T y)
{
return _predicate(x, y);
}
public int GetHashCode(T x)
{
return 0;
}
}
Your extension method:
public static class IEnumerableExntesion
{
public IEnumerable<T> Except<T>(this IEnumerable<T> source,
IEnumerable<T> second,
Func<T, T, bool> predicate)
{
return source.Except(second, new PredicateEqualityComparer(predicate));
}
}
Please note that I have stub GetHashCode implementation, it would be better to implement it properly but you would have to pass another delegate for it.
Short question:
Is there a simple way in LINQ to objects to get a distinct list of objects from a list based on a key property on the objects.
Long question:
I am trying to do a Distinct() operation on a list of objects that have a key as one of their properties.
class GalleryImage {
public int Key { get;set; }
public string Caption { get;set; }
public string Filename { get; set; }
public string[] Tags {g et; set; }
}
I have a list of Gallery objects that contain GalleryImage[].
Because of the way the webservice works [sic] I have duplicates of the
GalleryImage object. i thought it would be a simple matter to use Distinct() to get a distinct list.
This is the LINQ query I want to use :
var allImages = Galleries.SelectMany(x => x.Images);
var distinctImages = allImages.Distinct<GalleryImage>(new
EqualityComparer<GalleryImage>((a, b) => a.id == b.id));
The problem is that EqualityComparer is an abstract class.
I dont want to :
implement IEquatable on GalleryImage because it is generated
have to write a separate class to implement IEqualityComparer as shown here
Is there a concrete implementation of EqualityComparer somewhere that I'm missing?
I would have thought there would be an easy way to get 'distinct' objects from a set based on a key.
(There are two solutions here - see the end for the second one):
My MiscUtil library has a ProjectionEqualityComparer class (and two supporting classes to make use of type inference).
Here's an example of using it:
EqualityComparer<GalleryImage> comparer =
ProjectionEqualityComparer<GalleryImage>.Create(x => x.id);
Here's the code (comments removed)
// Helper class for construction
public static class ProjectionEqualityComparer
{
public static ProjectionEqualityComparer<TSource, TKey>
Create<TSource, TKey>(Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}
public static ProjectionEqualityComparer<TSource, TKey>
Create<TSource, TKey> (TSource ignored,
Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}
}
public static class ProjectionEqualityComparer<TSource>
{
public static ProjectionEqualityComparer<TSource, TKey>
Create<TKey>(Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}
}
public class ProjectionEqualityComparer<TSource, TKey>
: IEqualityComparer<TSource>
{
readonly Func<TSource, TKey> projection;
readonly IEqualityComparer<TKey> comparer;
public ProjectionEqualityComparer(Func<TSource, TKey> projection)
: this(projection, null)
{
}
public ProjectionEqualityComparer(
Func<TSource, TKey> projection,
IEqualityComparer<TKey> comparer)
{
projection.ThrowIfNull("projection");
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
this.projection = projection;
}
public bool Equals(TSource x, TSource y)
{
if (x == null && y == null)
{
return true;
}
if (x == null || y == null)
{
return false;
}
return comparer.Equals(projection(x), projection(y));
}
public int GetHashCode(TSource obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return comparer.GetHashCode(projection(obj));
}
}
Second solution
To do this just for Distinct, you can use the DistinctBy extension in MoreLINQ:
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector)
{
return source.DistinctBy(keySelector, null);
}
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
source.ThrowIfNull("source");
keySelector.ThrowIfNull("keySelector");
return DistinctByImpl(source, keySelector, comparer);
}
private static IEnumerable<TSource> DistinctByImpl<TSource, TKey>
(IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
foreach (TSource element in source)
{
if (knownKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
In both cases, ThrowIfNull looks like this:
public static void ThrowIfNull<T>(this T data, string name) where T : class
{
if (data == null)
{
throw new ArgumentNullException(name);
}
}
Building on Charlie Flowers' answer, you can create your own extension method to do what you want which internally uses grouping:
public static IEnumerable<T> Distinct<T, U>(
this IEnumerable<T> seq, Func<T, U> getKey)
{
return
from item in seq
group item by getKey(item) into gp
select gp.First();
}
You could also create a generic class deriving from EqualityComparer, but it sounds like you'd like to avoid this:
public class KeyEqualityComparer<T,U> : IEqualityComparer<T>
{
private Func<T,U> GetKey { get; set; }
public KeyEqualityComparer(Func<T,U> getKey) {
GetKey = getKey;
}
public bool Equals(T x, T y)
{
return GetKey(x).Equals(GetKey(y));
}
public int GetHashCode(T obj)
{
return GetKey(obj).GetHashCode();
}
}
This is the best i can come up with for the problem in hand.
Still curious whether theres a nice way to create a EqualityComparer on the fly though.
Galleries.SelectMany(x => x.Images).ToLookup(x => x.id).Select(x => x.First());
Create lookup table and take 'top' from each one
Note: this is the same as #charlie suggested but using ILookup - which i think is what a group must be anyway.
What about a throw away IEqualityComparer generic class?
public class ThrowAwayEqualityComparer<T> : IEqualityComparer<T>
{
Func<T, T, bool> comparer;
public ThrowAwayEqualityComparer(Func<T, T, bool> comparer)
{
this.comparer = comparer;
}
public bool Equals(T a, T b)
{
return comparer(a, b);
}
public int GetHashCode(T a)
{
return a.GetHashCode();
}
}
So now you can use Distinct with a custom comparer.
var distinctImages = allImages.Distinct(
new ThrowAwayEqualityComparer<GalleryImage>((a, b) => a.Key == b.Key));
You might be able to get away with the <GalleryImage>, but I'm not sure if the compiler could infer the type (don't have access to it right now.)
And in an additional extension method:
public static class IEnumerableExtensions
{
public static IEnumerable<TValue> Distinct<TValue>(this IEnumerable<TValue> #this, Func<TValue, TValue, bool> comparer)
{
return #this.Distinct(new ThrowAwayEqualityComparer<TValue>(comparer);
}
private class ThrowAwayEqualityComparer...
}
You could group by the key value and then select the top item from each group. Would that work for you?
This idea is being debated here, and while I'm hoping the .NET Core team adopt a method to generate IEqualityComparer<T>s from lambda, I'd suggest you to please vote and comment on that idea, and use the following:
Usage:
IEqualityComparer<Contact> comp1 = EqualityComparerImpl<Contact>.Create(c => c.Name);
var comp2 = EqualityComparerImpl<Contact>.Create(c => c.Name, c => c.Age);
class Contact { public Name { get; set; } public Age { get; set; } }
Code:
public class EqualityComparerImpl<T> : IEqualityComparer<T>
{
public static EqualityComparerImpl<T> Create(
params Expression<Func<T, object>>[] properties) =>
new EqualityComparerImpl<T>(properties);
PropertyInfo[] _properties;
EqualityComparerImpl(Expression<Func<T, object>>[] properties)
{
if (properties == null)
throw new ArgumentNullException(nameof(properties));
if (properties.Length == 0)
throw new ArgumentOutOfRangeException(nameof(properties));
var length = properties.Length;
var extractions = new PropertyInfo[length];
for (int i = 0; i < length; i++)
{
var property = properties[i];
extractions[i] = ExtractProperty(property);
}
_properties = extractions;
}
public bool Equals(T x, T y)
{
if (ReferenceEquals(x, y))
//covers both are null
return true;
if (x == null || y == null)
return false;
var len = _properties.Length;
for (int i = 0; i < _properties.Length; i++)
{
var property = _properties[i];
if (!Equals(property.GetValue(x), property.GetValue(y)))
return false;
}
return true;
}
public int GetHashCode(T obj)
{
if (obj == null)
return 0;
var hashes = _properties
.Select(pi => pi.GetValue(obj)?.GetHashCode() ?? 0).ToArray();
return Combine(hashes);
}
static int Combine(int[] hashes)
{
int result = 0;
foreach (var hash in hashes)
{
uint rol5 = ((uint)result << 5) | ((uint)result >> 27);
result = ((int)rol5 + result) ^ hash;
}
return result;
}
static PropertyInfo ExtractProperty(Expression<Func<T, object>> property)
{
if (property.NodeType != ExpressionType.Lambda)
throwEx();
var body = property.Body;
if (body.NodeType == ExpressionType.Convert)
if (body is UnaryExpression unary)
body = unary.Operand;
else
throwEx();
if (!(body is MemberExpression member))
throwEx();
if (!(member.Member is PropertyInfo pi))
throwEx();
return pi;
void throwEx() =>
throw new NotSupportedException($"The expression '{property}' isn't supported.");
}
}
Here's an interesting article that extends LINQ for this purpose...
http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx
The default Distinct compares objects based on their hashcode - to easily make your objects work with Distinct, you could override the GetHashcode method.. but you mentioned that you are retrieving your objects from a web service, so you may not be able to do that in this case.
implement IEquatable on GalleryImage because it is generated
A different approach would be to generate GalleryImage as a partial class, and then have another file with the inheritance and IEquatable, Equals, GetHash implementation.