todd.pund
todd.pund

Reputation: 689

Attempting to merge two Lists<> via Union. Still have duplicates

I have two Lists:

List<L1>, List<L2>

L1 = { detailId = 5, fileName = "string 1" }{ detailId = 5, fileName = "string 2" }
L2 = { detailId = 5, fileName = "string 2" }{ detailId = 5, fileName = "string 3" }

That I want to combine them with no duplicates:

List<L3>

L1 = { detailId = 5, fileName = "string 1" }{ detailId = 5, fileName = "string 2" }{ detailId = 5, fileName = "string 3" }

I've tried:

L1.Union(L2).ToList();
L1.Concat(L2).Distinct().ToList();

But both return with duplicates (1, 2, 2, 3).

Not sure what I'm missing.

edit Here's the method. It takes one list and creates another from a delimited string, and tries to combine them.

private List<Files> CombineList(int detailId, string fileNames)
{
    List<Files> f1 = new List<Files>();
    List<Files> f2 = new List<Files>();
    f1 = GetFiles(detailId, false);
    if (f1[0].fileNames != "")
    {
        string[] names = fileNames.Split('|');
        for (int i = 0; i < names.Length; i++)
        {
            Files x = new Files();
            x.detailId = detailId;
            x.fileNames = names[i];
            f2.Add(x);
        }
        List<Files> f3 = f1.Union(f2).ToList();
    }
    return f3;

}

Upvotes: 3

Views: 2508

Answers (5)

James Johnson
James Johnson

Reputation: 46047

If you're merging a list of objects, you will need to define some criteria for the equality comparisons. The example below demonstrates this:

class MyModelTheUniqueIDComparer : IEqualityComparer<MyModel>
{
    public bool Equals(MyModel x, MyModel y)
    {
        return x.SomeValue == y.SomeValue && x.OtherValue == y.OtherValue;
    }

    // If Equals() returns true for a pair of objects 
    // then GetHashCode() must return the same value for these objects.

    public int GetHashCode(MyModel myModel)
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 31 + myModel.SomeValue.GetHashCode();
            hash = hash * 31 + myModel.OtherValue.GetHashCode();
            return hash;
        }
    }
}

Then you can call to get the result:

var result = q1.Union(q2, new MyModelTheUniqueIDComparer());

Upvotes: 1

Alan
Alan

Reputation: 171

I don't like overriding the Files class equals object and getHashCode since you are interfering with the object. Let another object do that and just pass it in. This way if you have an issue with it later on, just swap it out and pass another IEqualityComparer Here's an example you can just test out

public void MainMethod()
{
    List<Files> f1 = new List<Files>() { new Files() { detailId = 5, fileName = "string 1" }, new Files() { detailId = 5, fileName = "string 2" } };
    List<Files> f2 = new List<Files>() { new Files() { detailId = 5, fileName = "string 2" }, new Files() { detailId = 5, fileName = "string 3" } };

    var f3 = f1.Union(f2, new FilesComparer()).ToList();
    foreach (var f in f3)
    {
    Console.WriteLine(f.detailId + " " + f.fileName);
    }

}

public class Files
{
    public int detailId;
    public string fileName;
}

public class FilesComparer : IEqualityComparer<Files>
{
    public bool Equals(Files x, Files y)
    {
        return x.fileName == y.fileName;
    }

    public int GetHashCode(Files obj)
    {
        return obj.fileName.GetHashCode();
    }
}

Upvotes: 3

S2S2
S2S2

Reputation: 8502

From MSDN Enumerable.Union Method:

If you want to compare sequences of objects of a custom data type, you have to implement the IEqualityComparer < T > generic interface in the class.

A sample implementation specific to your Files class so that the Union works correctly when merging two collections of custom type:

public class Files : IEquatable<Files>
{
    public string fileName { get; set; }
    public int detailId { get; set; }

    public bool Equals(Files other)
    {

        //Check whether the compared object is null. 
        if (Object.ReferenceEquals(other, null)) return false;

        //Check whether the compared object references the same data. 
        if (Object.ReferenceEquals(this, other)) return true;

        //Check whether the products' properties are equal. 
        return detailId.Equals(other.detailId) && fileName.Equals(other.fileName);
    }

    // If Equals() returns true for a pair of objects  
    // then GetHashCode() must return the same value for these objects. 

    public override int GetHashCode()
    {

        //Get hash code for the fileName field if it is not null. 
        int hashFileName = fileName == null ? 0 : fileName.GetHashCode();

        //Get hash code for the detailId field. 
        int hashDetailId = detailId.GetHashCode();

        //Calculate the hash code for the Files object. 
        return hashFileName ^ hashDetailId;
    }
}

Upvotes: 0

SolarBear
SolarBear

Reputation: 4619

From MSDN, for Union :

The default equality comparer, Default, is used to compare values of the types that implement the IEqualityComparer(Of T) generic interface. To compare a custom data type, you need to implement this interface and provide your own GetHashCode and Equals methods for the type.

link

Since you use a custom type, you need to either override the GetHashCode and Equals or provide an object that implements the IEqualityComparer interface (preferably IEquatable) and provide it as a second parameter to Union.

Here's a simple example of implementing such a class.

Upvotes: 6

Medinoc
Medinoc

Reputation: 6608

If your elements do not implement some kind of comparison interface (Object.Equals, IEquatable, IComparable, etc.) then any equality test between them will involve ReferenceEquals, in which two different objects are two different objects, even if all their members contain identical values.

Upvotes: 1

Related Questions