Domanda

I am trying to write a LINQ query on a collection of a complex type. I want to write a distinct query on this collection for a combination of two fields.

I don't think Lambda expression support piping of Distinct(f=>f.propertyname). Wish it did. Anyone used a simpler implementation then using a Comparer?

È stato utile?

Soluzione

You can use DistinctBy method in MoreLINQ package.

var result = items.DistinctBy(f => f.PropertyName);

You can DistinctBy with anonymous types to get distinct results by two columns.

var result = items.DistinctBy(f => new { f.Property1, f.Property2});

Altri suggerimenti

With pure LINQ you can group by all properties you need and then select first item from each group:

var result = items.GroupBy(i => new { i.Prop1, i.Prop2 })
                  .Select(g => g.First());

Pros:

  • You don't need to create custom comparer, or modify your class, or reference third party library.
  • It can be translated to SQL and executed on server side if you are querying items from database

Cons:

  • With Linq to Objects MoreLINQ approach is more efficient (though you should not do premature optimizations)
  • MoreLINQ syntax is more expressive

You cannot use distinct like that. Compiler couldn't know which of the multiple values to take. You can use GroupBy() to combine them or use Min(), Max(), First(), Last() or similar to select one from the list depending on your need.

You have several options:

  1. You could either implement a custom IEqualityComparer<T> for the overload of Distinct
  2. or override Equals + GethashCode in your class
  3. Another (less efficient) approach which doesn't need to create a new class or modify an existing is to use the builtin GetHashCode+Equals of an anonymous type and Enumerable.GroupBy:

    IEnumerable<Complex> distinctObjects = 
        from c in collection
        group c by  new { c.Prop1, c.Prop2 } into g 
        select g.First(); // change logic if you don't want an arbitrary object(first)
    

Here's an example of the second approach:

public class Complex
{
    public string Prop1 { get; set; }
    public int Prop2 { get; set; }
    public Complex Prop3 { get; set; }

    public override bool Equals(object obj)
    {
        Complex c2 = obj as Complex;
        if (obj == null) return false;
        return Prop1 == c2.Prop1 && Prop2 == c2.Prop2;
    }

    public override int GetHashCode()
    {
        unchecked // Overflow is fine, just wrap
        {
            int hash = 17;
            hash = hash * 23 + Prop1.GetHashCode();
            hash = hash * 23 + Prop2;
            return hash;
        }
    }
}

The Equals and GethashCode of the IEqualityComparer-class (1. approach) would be similar.

You could create an anonymous type on the fly with your two properties, and then use distinct.

Like this:

[Test]
public void Distinct_with_anonymous_type()
{
    var items = new[]
    {
        new {p1 = 'a', p2 = 2, p3=10},   // double over p1 and p2
        new {p1 = 'a', p2 = 3, p3=11}, 
        new {p1 = 'a', p2 = 2, p3=12},   // double over p1 and p2
    };

    var expected = new[]
    {
        new {p1 = 'a', p2 = 2},
        new {p1 = 'a', p2 = 3}, 
    };

    var distinct = items.Select(itm => new { itm.p1, itm.p2 }).Distinct();

    Assert.That(distinct, Is.EquivalentTo(expected));
}

Hope this helps!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top