简体   繁体   中英

Get list of distinct values in List<T> in c#

So, say I have something like the following:

public class Element
{
  public int ID;
  public int Type;
  public Properties prorerty;
  ...
}
and

List Elements = new List();

and I have a list of these:

 List Elements = new List(); 

What would be the cleanest way to get a list of all distinct values in the prorerty column in Element class? I mean, I could iterate through the list and add all values that aren't duplicates to another list of strings, but this seems dirty and inefficient. I have a feeling there's some magical Linq construction that'll do this in one line, but I haven't been able to come up with anything.

 var results = Elements.Distinct();

Note: you will have to override .Equals and .GetHashCode()

public class Element : IEqualityComparer<Element>
{
   public bool Equals(Element x, Element y)
   {
     if (x.ID == y.ID)
     {
        return true;
     }
     else
     {
        return false;
     }
   }
}

public int GetHashCode(Element obj)
{
    return obj.ID.GetHashCode();
}
var props = Elements.Select(x => x.Properties).Distinct();

And make sure you overridden .Equals() and .GetHashCode() methods.
Or if you need direct strings from Properties :

var props = Elements
    .Select(x => x.Properties != null ? x.Properties.Property : null)
    .Distinct();

If you need the string fields on the Properties field, and if you know the Properties field prorerty is never null , just use

IEnumerable<string> uniqueStrings = Elements
  .Select(e => e.prorerty.Property).Distinct();

If there's a chance prorerty can be null, handle that situation in the lambda.

This will use the default equality comparer for String which is an ordinal comparison independent of culture and case-sensitive.

my working example from LINQPad (C# Program)

void Main()
{
    var ret = new List<Element>();
    ret.Add(new Element(){ID=1});
    ret.Add(new Element(){ID=1});
    ret.Add(new Element(){ID=2});
    ret = ret.GroupBy(x=>x.ID).Select(x=>x.First()).ToList();
    Console.WriteLine(ret.Count()); // shows 2
}

public class Element
{
  public int ID;
  public int Type;
  public Properties prorerty; 
}

public class Properties
{
  public int Id;
  public string Property;

}

Isn't simpler to use one of the approaches shown below :) ? You can just group your domain objects by some key and select FirstOrDefault like below. This is a copy of my answer on similar question here: Get unique values - original answer

More interesting option is to create some Comparer adapter that takes you domain object and creates other object the Comparer can use/work with out of the box. Base on the comparer you can create your custom linq extensions like in sample below. Hope it helps :)

[TestMethod]
public void CustomDistinctTest()
{
    // Generate some sample of domain objects
    var listOfDomainObjects = Enumerable
                                .Range(10, 10)
                                .SelectMany(x => 
                                    Enumerable
                                    .Range(15, 10)
                                    .Select(y => new SomeClass { SomeText = x.ToString(), SomeInt = x + y }))
                                .ToList();

    var uniqueStringsByUsingGroupBy = listOfDomainObjects
                                    .GroupBy(x => x.SomeText)
                                    .Select(x => x.FirstOrDefault())
                                    .ToList();

    var uniqueStringsByCustomExtension = listOfDomainObjects.DistinctBy(x => x.SomeText).ToList();
    var uniqueIntsByCustomExtension = listOfDomainObjects.DistinctBy(x => x.SomeInt).ToList();

    var uniqueStrings = listOfDomainObjects
                            .Distinct(new EqualityComparerAdapter<SomeClass, string>(x => x.SomeText))
                            .OrderBy(x=>x.SomeText)
                            .ToList();

    var uniqueInts = listOfDomainObjects
                            .Distinct(new EqualityComparerAdapter<SomeClass, int>(x => x.SomeInt))
                            .OrderBy(x => x.SomeInt)
                            .ToList();
}

Custom comparer adapter:

public class EqualityComparerAdapter<T, V> : EqualityComparer<T>
    where V : IEquatable<V>
{
    private Func<T, V> _valueAdapter;

    public EqualityComparerAdapter(Func<T, V> valueAdapter)
    {
        _valueAdapter = valueAdapter;
    }

    public override bool Equals(T x, T y)
    {
        return _valueAdapter(x).Equals(_valueAdapter(y));
    }

    public override int GetHashCode(T obj)
    {
        return _valueAdapter(obj).GetHashCode();
    }
}

Custom linq extension (definition of DistinctBy extension method):

// Embed this class in some specific custom namespace
public static class DistByExt
{
    public static IEnumerable<T> DistinctBy<T,V>(this IEnumerable<T> enumerator,Func<T,V> valueAdapter)
        where V : IEquatable<V>
    {
        return enumerator.Distinct(new EqualityComparerAdapter<T, V>(valueAdapter));
    }
}

Definition of domain class used in test case:

public class SomeClass
{
    public string SomeText { get; set; }
    public int SomeInt { get; set; }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM