Get distinct list values

2020-02-11 02:05发布

i have a C# application in which i'd like to get from a List of Project objects , another List which contains distinct objects.

i tried this

 List<Project> model = notre_admin.Get_List_Project_By_Expert(u.Id_user);
 if (model != null) model = model.Distinct().ToList();

The list model still contains 4 identical objects Project.

What is the reason of this? How can i fix it?

8条回答
ら.Afraid
2楼-- · 2020-02-11 02:22

Here's an answer from basically the same question that will help.

Explanation:

The Distinct() method checks reference equality for reference types. This means it is looking for literally the same object duplicated, not different objects which contain the same values.

Credits to @Rex M.

查看更多
何必那么认真
3楼-- · 2020-02-11 02:25

The object's reference aren't equal. If you want to be able to do that on the entire object itself and not just a property, you have to implement the IEqualityComparer or IEquatable<T>.

查看更多
家丑人穷心不美
4楼-- · 2020-02-11 02:29

How do you define identical? You should override Equals in Project with this definition (if you override Equals also override GetHashCode). For example:

public class Project
{
    public int ProjectID { get; set; }

    public override bool Equals(object obj)
    {
        var p2 = obj as Project;
        if (p2 == null) return false;
        return this.ProjectID == m2.ProjectID;
    }

    public override int GetHashCode()
    {
        return ProjectID;
    }
}

Otherwise you are just checking reference equality.

查看更多
狗以群分
5楼-- · 2020-02-11 02:35
var newList = 
(
from x in model
select new {Id_user= x.Id_user}
).Distinct();

or you can write like this

var list1 = model.DistinctBy(x=> x.Id_user);
查看更多
Rolldiameter
6楼-- · 2020-02-11 02:35

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.

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):

// Embedd 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 object used in test case:

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

}
查看更多
干净又极端
7楼-- · 2020-02-11 02:37

You need to define "identical" here. I'm guessing you mean "have the same contents", but that is not the default definition for classes: the default definition is "are the same instance".

If you want "identical" to mean "have the same contents", you have two options:

  • write a custom comparer (IEqualityComparer<Project>) and supply that as a parameter to Distinct
  • override Equals and GetHashCode on Project

There are also custom methods like DistinctBy that are available lots of places, which is useful if identity can be determined by a single property (Id, typically) - not in the BCL, though. But for example:

if (model != null) model = model.DistinctBy(x => x.Id).ToList();

With, for example:

public static IEnumerable<TItem>
    DistinctBy<TItem, TValue>(this IEnumerable<TItem> items,
    Func<TItem, TValue> selector)
{
    var uniques = new HashSet<TValue>();
    foreach(var item in items)
    {
        if(uniques.Add(selector(item))) yield return item;
    }
}
查看更多
登录 后发表回答