0

I'm desperately trying to delete all the items with a list of the same value inside.

Here's the code:

    private void Button_deleteDouble_MouseDown(object sender, EventArgs e)
    {
        boardGenerate.Add(new BoardInformation(146, new List<string> { "test" }));
        boardGenerate.Add(new BoardInformation(545, new List<string> { "test" }));

        boardGenerate = boardGenerate.DistinctBy(x => x.positionQueen).ToList();
    }

Normally, since the two lists inside the object are the same, the .DistinctBy() command should remove one of the two objects.

But no, my object list still has the same two objects with the same list

.positionQueen is the name of the variable containing the list

Could somebody help me?

Edit :

The DistinctBy() method comes from MoreLinq.

And this is my BoardInformation class:

public class BoardInformation
{
    public BoardInformation(int nbQueen, List<string> positionQueen)
    {
        this.nbQueen = nbQueen;
        this.positionQueen = positionQueen;
    }

    public int nbQueen { get; set; }
    public List<string> positionQueen { get; set; }     
}
6
  • 5
    It doesn't help that we don't know anything about BoardInformation. Please provide a minimal reproducible example. But note that List<T> doesn't override Equals or GetHashCode. If positionQueen is the List<string>, you probably need to provide an IEqualityComparer<List<string>> to the DistinctBy call. (It would also help if you'd say which DistinctBy method this is - is it MoreLINQ?) Commented Feb 9, 2020 at 19:22
  • I did an edit. I don't understand what you mean by Equal and GetHashCode at all, can you explain it better? Commented Feb 9, 2020 at 19:30
  • You're adding 2 things and then selecting using Distinct, why would it delete? Commented Feb 9, 2020 at 19:31
  • 2
    You have two lists. They have equal content, but try printing out boardGenerate[0].positionQueen.Equals(boardGenerate[1].positionQueen) and it will print False, because List<T> doesn't override Equals - and that's what DistinctBy uses to find equal values. Commented Feb 9, 2020 at 19:34
  • 4
    As an aside, I'd strongly recommend starting to follow .NET naming conventions Commented Feb 9, 2020 at 19:35

3 Answers 3

3

Set-based operations like Distinct and DistinctBy need a way of determining whether two values are the same. You're using DistinctBy, so you're already asking MoreLINQ to compare the "inner lists" for equality - but you're not saying how to do that.

List<T> doesn't override Equals or GetHashCode, which means it inherits the reference equality behaviour from System.Object. In other words, if you create two separate List<T> objects, they won't compare as equal, even if they have the same content. For example:

List<int> list1 = new List<int>();
List<int> list2 = new List<int>();
Console.WriteLine(list1.Equals(list2)); // False

You need to tell DistinctBy how you want to compare the two lists, using an IEqualityComparer<T> - where T in this case is List<string> (because that's the type of BoardInformation.positionQueen.

Here's an example of a generic ListEqualityComparer you could use:

using System;
using System.Collections.Generic;
using System.Linq;

public sealed class ListEqualityComparer<T> : IEqualityComparer<List<T>>
{
    private readonly IEqualityComparer<T> elementComparer;

    public ListEqualityComparer(IEqualityComparer<T> elementComparer) =>
        this.elementComparer = elementComparer;

    public ListEqualityComparer() : this(EqualityComparer<T>.Default)
    {
    }

    public bool Equals(List<T> x, List<T> y) =>
        ReferenceEquals(x, y) ? true
        : x is null || y is null ? false
        // Delegate to LINQ's SequenceEqual method
        : x.SequenceEqual(y, elementComparer);

    public int GetHashCode(List<T> obj)
    {
        if (obj is null)
        {
            return 0;
        }
        // Just a very simple hash implementation
        int hash = 23;
        foreach (var item in obj)
        {
            hash = hash * 31 +
                (item is null ? 0
                 : elementComparer.GetHashCode(item));
        }
        return hash;
    }
}

You'd then pass that to DistinctBy, like this:

// We're fine to use the default *element* comparer (string.Equals etc)
var comparer = new ListEqualityComparer<string>();
boardGenerate = boardGenerate.DistinctBy(x => x.positionQueen, comparer).ToList();

Now DistinctBy will call into the comparer, passing in the lists, and will consider your two BoardInformation objects are equal - so only the first will be yielded by DistinctBy, and you'll end up with a list containing a single item.

Sign up to request clarification or add additional context in comments.

Comments

1

It comes down to whether a equality check is using referential equality or value equality...you want value equality based on a specific property and that has to be done by hand.

When there is no IEqualityComparer provided which can used to compare individual objects (which is need by the Distinct call), the system determines the equality from each item's references by using their derived object low level service method call of GetHashCode from each reference; hence a reference difference is done and all your values in the list are unique (not equal) regardless of similar property values.

What you are looking for is to have value equality checked specifically for the nbQueenProperty.


To fully utilize Distinct one must create a IEqualityComparer and modify the GetHashCode. By specifing the hash value which can make objects equal...you can weed out the same positionQueen (or other properties) instances out.

Example

public class MyClass
{
    public string Name { get; set; }
    public int nbQueen { get; set; }
}

Equality comparer to weed out all nbQueen similarities:

class ContactEmailComparer : IEqualityComparer < MyClass >
{
    public bool Equals(MyClass x, MyClass y)
    {
      return x.nbQueen.Equals(y.nbQueen); // Compares by calling each `GetHashCode` 
    }

    public int GetHashCode(MyClass obj)
    {
        return obj.nbQueen.GetHashCode(); // Add or remove other properties as needed.
    }
}

Test code

var original = new List<MyClass>()
{
    new MyClass() { nbQueen = 1, Name="Alpha"   },
    new MyClass() { nbQueen = 1, Name="Omega" },
    new MyClass() { nbQueen = 3, Name="Delta" }

};

IEqualityComparer<MyClass> comparer = new ContactEmailComparer();

var newOne = original.Distinct( comparer ).ToList();

Result of the value of newOne :

enter image description here


To be clear...

... .DistinctBy() command should remove one of the two objects.

Does not remove anything. It returns a reference to a new list that should be distinct via the equality operation. The original list (the reference to it) does not change.

5 Comments

"it compares by reference values" - well, it calls Equals/GetHashCode, which by default uses reference identity for equality. But if the type itself overrides Equals/GetHashCode, that's a different matter. I think the first paragraph could be clarified.
@JonSkeet Fair enough. Updated first section to differentiate between reference equality operation done by default (which uses GetHashCode off of Object) against the perceived value equality the user desires.
Well it uses Equals as well as GetHashCode - an implementation which doesn't use Equals can't be correct. I have to say that if I didn't already know what was going on, I'd find it hard to follow your explanation - and in particular, you seem to be assuming the OP wants equality in terms of nbQueen, but there's no indication that that's what they actually want to do. Indeed, they specifically say they want to compare the lists. If the OP really did just want to compare by nbQueen, they could use boardGenerate.DistinctBy(x => x.nbQueen) - no custom comparers involved at all.
At minimum I've given the OP a guide on how to use Distinct ...which hopefully answers the question in the short term. I base the request by the OP mentioning that that boardGenerate = boardGenerate.DistinctBy(x => x.positionQueen).ToList(); which the OP claims does not work (for the situation?) : "But no, my object list still has the same two objects with the same list"
Yes, it doesn't work because List<T> uses reference equality by default. Providing an IEqualityComparer<List<T>> as a second argument to DistinctBy would work fine. I don't see why you would assume that the OP is using entirely the wrong property. The example they've given doesn't even use the same value for nbQueen for both values, so your code would give the same result (considering the two objects to be unequal, when the OP wants to consider them as equal).
0

LINQ solution

because you have another List inside your class you can not use District or DistrictBy, alternatively, you can use LINQ to filter the list.

     boardGenerate = (from b in boardGenerate
                      from l in b.positionQueen
                      group new { l,b } by l into g
                      select g.First().b
                      ).ToList();
   // this returns just first duplicate item like district

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.