Kurisuchin
Kurisuchin

Reputation: 155

Comparing two lists and return not matching items results with error

I tried to compare two lists by using the Except method. But when I did, I got an error saying:

Cannot convert from 'Systems.Collections.Generic.List<>' to 'System.Linq.IQueryable<>'

'System.Collections.Generic.List<> does not contain a definition for 'Except' and the best extension method overload 'System.Linq.Queryable.Except(System.Linq.IQueryable, System.Collections.GEneric.IEnumerable)' has some invalid arguments

I also experienced this when I tried Intersect. I'm trying to compare Sent list and Result list (code and list shown below) and return items that does not have any match. So when I googled for how to do so, I came across the Except method as well as the Intersect.

public class Sent
{
    public string Address;
    public string Data;
}

public class Result
{
    public string AddressOK;
    public string DataOK;
}



var sent = new List<Sent>();
sent.Add(new Sent() { Address = linaddr1, Data = lindat1 });
var res = new List<Result>();
res.Add( new Result() { AddressOK = linaddr2, DataOK = lindat2 } );
//linaddr1 and 2, lindat1 and 2 contains the address and data shown in the list below
//taken from another part of the entire program

The lists look like such:

sent                       res
Address    Data            Address        Data
04004C     55AA55          04004C         55AA55
040004     0720            040004         0720
040037     30           
04004A     FFFF            04004A         FFFF

I only tried using this code: var diff = sent.Except(res).ToList() but as I've mentioned, it results with the aforementioned errors above.

EDIT: I edited the list. Sorry for that. It's just only a matter of the res list missing one or two or more items from the original list and then comparing both lists to see which item/s is/are missing from the res list.

Upvotes: 1

Views: 5945

Answers (4)

Naveen Tiwari
Naveen Tiwari

Reputation: 361

@Kurisuchin Suppose you have 2 list and in both you have ID property based on which you want to compare both list and want to store non matching item in third list. In this Situation following Linq Query can help out.

var result = List2.Where(p => !List1.Any(p2 => p2.ID == p.ID)).ToList();

Upvotes: 0

Michael Buen
Michael Buen

Reputation: 39393

If you are comfortable using an AOP component to automate the manual code of implementing IEquatable, another approach would be is to use Equals.Fody:

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

public class Program
{
    public static void Main()
    {
        var a = new Sent { Address = "04004C", Data = "55AA55" };
        var b = new Sent { Address = "04004C", Data = "55AA55" };

        Console.WriteLine(a.Equals(b)); // True with use of an AOP, False with no AOP           

        var sent = new List<Sent>() {
            new Sent { Address = "04004C", Data = "55AA55" },
            new Sent { Address = "040004", Data = "0720" },             
            new Sent { Address = "040037", Data = "31" },               
            new Sent { Address = "04004A", Data = "FFFF" }
        };

        var res = new List<Result>() {
            new Result { AddressOK = "04004C", DataOK = "55AA55" },
            new Result { AddressOK = "040004", DataOK = "0721" },               
            new Result { AddressOK = "040038 ", DataOK = "31" },                
            new Result { AddressOK = "04004A", DataOK = "FFFF" }    
        };


        var diff =
            sent.Except(
                res.Select(r => new Sent { Address = r.AddressOK, Data = r.DataOK })
            );

        foreach (var item in diff)
            Console.WriteLine("{0} {1}", item.Address, item.Data);           

    }
}


[Equals]
public class Sent
{
    public string Address;
    public string Data;

    [CustomEqualsInternal]
    bool CustomLogic(Sent other)
    {
        return other.Address == this.Address && other.Data == this.Data;
    }
}

public class Result
{
    public string AddressOK;
    public string DataOK;
}

Output:

True
040004 0720
040037 31

If you'll do map Result to Sent very often, you can further shorten your Linq query code to..

var diff = sent.Except(res.Select(r => (Sent)r));

..by automating the mapping of Result to Sent, use implicit operator:

[Equals]
public class Sent
{
    public string Address;
    public string Data;

    [CustomEqualsInternal]
    bool CustomLogic(Sent other)
    {
        return other.Address == this.Address && other.Data == this.Data;
    }


    public static implicit operator Sent(Result r)
    {
        return new Sent { Address = r.AddressOK, Data = r.DataOK };
    }        
}

Upvotes: 0

Michael Buen
Michael Buen

Reputation: 39393

Use Any:

using System;

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

public class Program
{
    public static void Main()
    {

        var sent = new List<Sent>()
        {
            new Sent { Address = "04004C", Data = "55AA55" },
            new Sent { Address = "040004", Data = "0720" },             
            new Sent { Address = "040037", Data = "31" },               
            new Sent { Address = "04004A", Data = "FFFF" }
        };


        var res = new List<Result> () {
            new Result { AddressOK = "04004C", DataOK = "55AA55" },
            new Result { AddressOK = "040004", DataOK = "0721" },               
            new Result { AddressOK = "040038 ", DataOK = "31" },                
            new Result { AddressOK = "04004A", DataOK = "FFFF" }

        };

        var diff =
            sent.Where (s => !res.Any (r => s.Address == r.AddressOK && s.Data == r.DataOK ));


        foreach (var item in diff) 
        {
            Console.WriteLine("{0} {1}", item.Address, item.Data);
        }

    }
}


public class Sent
{
    public string Address;
    public string Data;
}


public class Result
{
    public string AddressOK;
    public string DataOK;
}

Output:

040004 0720
040037 31

Live Code: https://dotnetfiddle.net/ZVuiPd

Upvotes: 2

Enigmativity
Enigmativity

Reputation: 117064

The types Sent and Result are distinct types, but sent.Except(res) expects them to be the same. That's your first mistake.

The following is a simple (but incorrect) fix:

var diff =
    sent
        .Except(res.Select(x => new Sent() { Address = x.AddressOK, Data = x.DataOK }))
        .ToList();

Even though this compiles, and runs, it doesn't remove the duplicates because your Sent doesn't override GetHashCode and Equals, hence it only compares references and not the actual properties.

You can either implement GetHashCode and Equals, or create an IEqualityComparer<Sent> to get this to work.

An IEqualityComparer<Sent> implementation might look like this:

public class SentEqualityComparer : IEqualityComparer<Sent>
{
    public int GetHashCode(Sent sent)
    {
        return sent.Address.GetHashCode() ^ sent.Data.GetHashCode();
    }

    public bool Equals(Sent left, Sent right)
    {
        return (left.Address == right.Address) && (left.Data == right.Data);
    }
}

And you would use it like so:

var diff =
    sent
        .Except(
            res.Select(x => new Sent() { Address = x.AddressOK, Data = x.DataOK }),
            new SentEqualityComparer())
        .ToList();

This works as you expect.

The other option, to override GetHashCode and Equals, comes with an additional hurdle. The result of GetHashCode should not ever change throughout the lifetime of the object otherwise you can't use the object in a dictionary or any other data structure that relies on the hash code.

So, to make it work, you need to change Address & Data to be read-only.

Here is an implementation of your Sent class that will work correctly:

public sealed class Sent : IEquatable<Sent>
{
    private readonly string _Address; 
    private readonly string _Data;

    public string Address { get { return _Address; } } 
    public string Data { get { return _Data; } }

    public Sent(string Address, string Data)
    {
        _Address = Address; 
        _Data = Data;    
    }

    public override bool Equals(object obj)
    {
        if (obj is Sent)
                return Equals((Sent)obj);
        return false;
    }

    public bool Equals(Sent obj)
    {
        if (obj == null) return false;
        if (!EqualityComparer<string>.Default.Equals(_Address, obj._Address)) return false; 
        if (!EqualityComparer<string>.Default.Equals(_Data, obj._Data)) return false;    
        return true;
    }

    public override int GetHashCode()
    {
        int hash = 0;
        hash ^= EqualityComparer<string>.Default.GetHashCode(_Address); 
        hash ^= EqualityComparer<string>.Default.GetHashCode(_Data);
        return hash;
    }
}

Upvotes: 2

Related Questions