2

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.

marko
  • 10,684
  • 17
  • 71
  • 92

6 Answers6

2
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() )
Louis Ricci
  • 20,804
  • 5
  • 48
  • 62
2

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);
        }
    }
Tejs
  • 40,736
  • 10
  • 68
  • 86
  • At first glance I'm not sure your generic equality comparer will work. How to you guarantee that `GetHashCode()` will be equal for two objects that are `Equal`? – D Stanley Oct 25 '12 at 18:24
  • I'm relying on the actual implementation of each object's GetHashCode. If it becomes important, have the `TSource` type override `GetHashCode` and provide a specific implementation. – Tejs Oct 25 '12 at 18:25
  • In your first extension I would definitely use an `HashSet`, rather than a `Dictionary` – Paolo Moretti Oct 25 '12 at 18:28
  • @Tejs Then you're disconnecting Equals and GetHashCode, which breaks collections that compare hash codes first. For example with `new GenericEqualitySelector((i , j) => i == j + 1);` 2 == 1 but their hash codes will be different. – D Stanley Oct 25 '12 at 18:33
  • I see. The extension could be easily modified with a hash code selector then. I'll update the solution. – Tejs Oct 25 '12 at 19:05
1

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();
    }
}
D Stanley
  • 149,601
  • 11
  • 178
  • 240
1

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());
Mario S
  • 11,715
  • 24
  • 39
  • 47
1

Much simpler code using GroupBy instead:

@foreach (Foto f in fotos.GroupBy(f => f.smallurl).Select(g => g.First()))
Zaid Masud
  • 13,225
  • 9
  • 67
  • 88
0

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();
    }

    }
Uriil
  • 11,948
  • 11
  • 47
  • 68