11

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?

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Lamloumi Afif
  • 8,941
  • 26
  • 98
  • 191

8 Answers8

12

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;
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
7
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);
Neeraj Dubey
  • 4,401
  • 8
  • 30
  • 49
5

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.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
  • By this, I *think* @Henk means "the equality / hash should be appropriate for your type's definition of equality, which may ***or may not*** be limited to just one property like the `ProjectId`" – Marc Gravell Oct 23 '13 at 10:40
  • @MarcGravell: I don't know OP's `Project` class so i've presumed something db-related which has an identifier like `...Id`. Why should i complicate things before OP has clarified his definition of _identical_? – Tim Schmelter Oct 23 '13 at 10:41
  • What @MarcGravell said, plus Equality should not be overridden for mutable objects. The ProjectID is probably logically immutable, making this example just acceptable. But add a Name property and it'll end in tears. – H H Oct 23 '13 at 10:44
  • @Henk indeed, mutability and dictionaries (etc) that span more than a single snapshot of time are a recipe for pain. – Marc Gravell Oct 23 '13 at 10:45
  • But it may also be desired to find projects (in a list or dictionary) via ID even if a name was changed. So why must **all** properties be identical to identify an object? That doesn't make sense to me. – Tim Schmelter Oct 23 '13 at 10:50
  • No, all identifying properties should be immutable. The OP just isn't clear about this. But changing Equality has project-wide consequences, a simple Comparer is what's asked for here. – H H Oct 23 '13 at 11:14
4

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>.

Jeffrey Kevin Pry
  • 3,266
  • 3
  • 35
  • 67
3

Check this example: you need to use either Comparator or override Equals()

class Program
{
    static void Main( string[] args )
    {
        List<Item> items = new List<Item>();
        items.Add( new Item( "A" ) );
        items.Add( new Item( "A" ) );
        items.Add( new Item( "B" ) );
        items.Add( new Item( "C" ) );

        items = items.Distinct().ToList();
    }
}

public class Item
{
    string Name { get; set; }
    public Item( string name )
    {
        Name = name;
    }

    public override bool Equals( object obj )
    {
        return Name.Equals((obj as Item).Name);
    }
    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}
3

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.

Community
  • 1
  • 1
Abbas
  • 14,186
  • 6
  • 41
  • 72
  • This isn't correct, though; the `Distinct()` method (without a custom equality comparer) **simply applies that type's own definition of equality**. It is the *type* that determines the meaning of equality, not `Distinct()` – Marc Gravell Oct 23 '13 at 10:41
  • Didn't think of it that way. :) I just found the answer where he uses the custom comparer and I figured this was exact what he was looking for. – Abbas Oct 23 '13 at 10:47
0

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

}
0
List<ViewClReceive> passData = (List<ViewClReceive>)TempData["passData_Select_BankName_List"];
    passData = passData?.DistinctBy(b=>b.BankNm).ToList();

It will Works ......

Uzzal Prasad
  • 115
  • 1
  • 11