Reputation: 155
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
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
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
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
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