JDoshi
JDoshi

Reputation: 315

How to use C# LINQ Union to get the Union of Custom list1 with list2

I am using the Enumerable.Union<TSource> method to get the union of the Custom List1 with the Custom List2. But somehow it does not work as it should in my case. I am getting all the items also the duplicate once.

I followed the MSDN Link to get the work done, but still I am not able to achieve the same.

Following is the Code of the custom class:-

public class CustomFormat : IEqualityComparer<CustomFormat>
{
    private string mask;

    public string Mask
    {
        get { return mask; }
        set { mask = value; }
    }

    private int type;//0 for Default 1 for userdefined

    public int Type
    {
         get { return type; }
         set { type = value; }
    }
    public CustomFormat(string c_maskin, int c_type)
    {
        mask = c_maskin;
        type = c_type;
    }

    public bool Equals(CustomFormat x, CustomFormat y)
    {
        if (ReferenceEquals(x, y)) return true;

        //Check whether the products' properties are equal. 
        return x != null && y != null && x.Mask.Equals(y.Mask) && x.Type.Equals(y.Type);
    }

    public int GetHashCode(CustomFormat obj)
    {
        //Get hash code for the Name field if it is not null. 
        int hashProductName = obj.Mask == null ? 0 : obj.Mask.GetHashCode();

        //Get hash code for the Code field. 
        int hashProductCode = obj.Type.GetHashCode();

        //Calculate the hash code for the product. 
        return hashProductName ^ hashProductCode;
    }
}

This I am calling as follows:-

List<CustomFormat> l1 = new List<CustomFormat>();
l1.Add(new CustomFormat("#",1));
l1.Add(new CustomFormat("##",1));
l1.Add(new CustomFormat("###",1));
l1.Add(new CustomFormat("####",1));

List<CustomFormat> l2 = new List<CustomFormat>();
l2.Add(new CustomFormat("#",1));
l2.Add(new CustomFormat("##",1));
l2.Add(new CustomFormat("###",1));
l2.Add(new CustomFormat("####",1));
l2.Add(new CustomFormat("## ###.0",1));

l1 = l1.Union(l2).ToList();

foreach(var l3 in l1)
{
    Console.WriteLine(l3.Mask + " " + l3.Type);
}

Please suggest the appropriate way to achieve the same!

Upvotes: 9

Views: 2519

Answers (2)

Jehof
Jehof

Reputation: 35544

You need to pass an instance of an IEqualityComparer to the Union method. The method has an overload to pass in your comparer.

The easiest and ugliest solution is

var comparer = new CustomFormat(null,0);

l1 = l1.Union(l2, comparer).ToList();

You have made some mistakes in your implementation. You should not implement the IEqualityComparer method on your type (CustomFormat), but on a separate class, like CustomFormatComparer.

On your type (CustomFormat) you should implemented IEquatable.

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1500155

The oddity here is that your class implement IEqualityComparer<CustomClass> instead of IEquatable<CustomClass>. You could pass in another instance of CustomClass which would be used as the comparer, but it would be more idiomatic to just make CustomClass implement IEquatable<CustomClass>, and also override Equals(object).

The difference between IEquatable<T> and IEqualityComparer<T> is that IEquatable<T> says "I know how to compare myself with another instance of T" whereas IEqualityComparer<T> says "I know how to compare two instances of T". The latter is normally provided separately - just as it can be provided to Union via another parameter. It's very rare for a type to implement IEqualityComparer<T> for its own type - whereas IEquatable<T> should pretty much only be used to compare values of the same type.

Here's an implementation using automatically implemented properties for simplicity and more idiomatic parameter names. I'd probably change the hash code implementation myself and use expression-bodied members, but that's a different matter.

public class CustomFormat : IEquatable<CustomFormat>
{
    public string Mask { get; set; }
    public int Type { get; set; }

    public CustomFormat(string mask, int type)
    {
        Mask = mask;
        Type = type;
    }

    public bool Equals(CustomFormat other)
    {
        if (ReferenceEquals(this, other))
        {
            return true;
        }
        return other != null && other.Mask == Mask && other.Type == Type;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as CustomFormat);
    }

    public override int GetHashCode()
    {
        // Get hash code for the Name field if it is not null. 
        int hashProductName = Mask == null ? 0 : Mask.GetHashCode();

        //Get hash code for the Code field. 
        int hashProductCode = Type.GetHashCode();

        //Calculate the hash code for the product. 
        return hashProductName ^ hashProductCode;
    }
}

Now it doesn't help that (as noted in comments) the documentation for Enumerable.Union is wrong. It currently states:

The default equality comparer, Default, is used to compare values of the types that implement the IEqualityComparer<T> generic interface.

It should say something like:

The default equality comparer, Default, is used to compare values when a specific IEqualityComparer<T> is not provided. If T implements IEquatable<T>, the default comparer will use that implementation. Otherwise, it will use the implementation of Equals(object).

Upvotes: 9

Related Questions