IEqualityComparer for Annoymous Type - c#

Firstly I have seen IEqualityComparer for anonymous type and the answers there do not answer my question, for the obvious reason that I need an IEqualityComparer not and IComparer for use with Linq's Distinct() method. I have checked the other answers too and these fall short of a solution...
The Problem
I have some code to manipulate and pull records in from a DataTable
var glext = m_dtGLExt.AsEnumerable();
var cflist =
(from c in glext
orderby c.Field<string>(m_strpcCCType),
c.Field<string>(m_strpcCC),
c.Field<string>(m_strpcCCDesc),
c.Field<string>(m_strpcCostItem)
select new
{
CCType = c.Field<string>(m_strpcCCType),
CC = c.Field<string>(m_strpcCC),
CCDesc = c.Field<string>(m_strpcCCDesc),
CostItem = c.Field<string>(m_strpcCostItem)
}).Distinct();
but I need the distinct method to be case insensitive. What is throwing me here is the use of anonymous types.
Attempted Solution 1
If I had SomeClass which had concrete objects I could obviously do
public class SumObject
{
public string CCType { get; set; }
public string CC { get; set; }
public string CCDesc { get; set; }
public string CostItem { get; set; }
}
I could obviously do this
List<SumObject> lso = new List<SumObject>()
{
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Rooney", CostItem = "I477" },
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Zidane", CostItem = "I677" },
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Falcao", CostItem = "I470" },
};
var e = lso.Distinct(new SumObjectComparer()); // Great :]
where
class SumObjectComparer : IEqualityComparer<SumObject>
{
public bool Equals(SumObject x, SumObject y)
{
if (Object.ReferenceEquals(x, y))
return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.CCType.CompareNoCase(y.CCType) == 0 &&
x.CC.CompareNoCase(y.CC) == 0 &&
x.CCDesc.CompareNoCase(y.CCDesc) == 0 &&
x.CostItem.CompareNoCase(y.CostItem) == 0;
}
public int GetHashCode(SumObject o)
{
if (Object.ReferenceEquals(o, null))
return 0;
int hashCCType = String.IsNullOrEmpty(o.CCType) ?
0 : o.CCType.ToLower().GetHashCode();
int hashCC = String.IsNullOrEmpty(o.CC) ?
0 : o.CC.ToLower().GetHashCode();
int hashCCDesc = String.IsNullOrEmpty(o.CCDesc) ?
0 : o.CCDesc.ToLower().GetHashCode();
int hashCostItem = String.IsNullOrEmpty(o.CostItem) ?
0 : o.CostItem.ToLower().GetHashCode();
return hashCCType ^ hashCC ^ hashCCDesc ^ hashCostItem;
}
}
However, the use of anonymous types in the above Linq query are throwing me.
Attempted Solution 2
To attempt another solution to this (and because I have the same issue elsewhere) I generated the following generic comparer class
public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
Func<T, T, bool> compareFunction;
Func<T, int> hashFunction;
public GenericEqualityComparer(Func<T, T, bool> compareFunction, Func<T, int> hashFunction)
{
this.compareFunction = compareFunction;
this.hashFunction = hashFunction;
}
public bool Equals(T x, T y) { return compareFunction(x, y); }
public int GetHashCode(T obj) { return hashFunction(obj); }
}
so that I could attempt to do
var comparer = new GenericEqualityComparer<dynamic>(
(x, y) => { /* My equality stuff */ },
o => { /* My hash stuff */ });
but this casts the returned value as IEnumerable<dynamic> which in turn effects my forthcoming use of cflist, so that in a following query the join fails.
var cf =
(from o in cflist
join od in glext
on new { o.CCType, o.CC, o.CCDesc, o.CostItem } equals new
{
CCType = od.Field<string>(m_strpcCCType),
CC = od.Field<string>(m_strpcCC),
CCDesc = od.Field<string>(m_strpcCCDesc),
CostItem = od.Field<string>(m_strpcCostItem)
}
into c
select new { ... }
I don't want to get into ugly casting to and from IEnumerable<T>s due to the heavy use of this code...
Question
Is there a way I can create my an IEquailityComparer for my anonymous types?
Thanks for your time.

Is there a way I can create my an IEquailityComparer for my anonymous types?
Sure. You just need to use type inference. For example, you could have something like:
public static class InferredEqualityComparer
{
public static IEqualityComparer<T> Create<T>(
IEnumerable<T> example,
Func<T, T, bool> equalityCheck,
Func<T, int> hashCodeProvider)
{
return new EqualityComparerImpl<T>(equalityCheck, hashCodeProvider);
}
private sealed class EqualityComparerImpl<T> : IEqualityComparer<T>
{
// Implement in the obvious way, remembering the delegates and
// calling them appropriately.
}
}
Then:
var glext = m_dtGLExt.AsEnumerable();
var query = from c in glext
orderby ...
select new { ... };
var comparer = InferredEqualityComparer.Create(query,
(x, y) => { ... },
o => { ... }
);
var distinct = query.Distinct(comparer);
Basically the first parameter to the method is just used for type inference, so that the compiler can work out what type to use for the lambda expression parameters.
You could create the comparer ahead of time by creating a sample of the anonymous type:
var sample = new[] { new { ... } };
var comparer = InferredExqualityComparer.Create(sample, ...);
var distinct = (... query here ... ).Distinct(comparer);
but then any time you change the query you've got to change the sample too.

This post may get what you want. Although for .NET 2.0 it also works for newer versions (see the bottom of this post for how to achieve this). In contrast to Jon Skeets solution we won´t use a factory-method like create. But this is only syntactic sugar I think.

Related

Simplest way to form union of two Lists of objects (Containing Int and string values)

I saw a similar question here with a very good solutions:
Simplest way to form a union of two lists
But the problem here is, it works when there is only one parameter in each list (int value). I had this rquirement to combine 5 different lists containing objects of the same class without data redundancy and the final list should be sorted out in ascending order of int value.
Example:
Class Company //data Class
{
int companyNo;
string Name;
}
Class CompanyList : List<Company>
{
.................
public CompanyList GetList(int userID)
{
.....
}
}
Class company has a pulic method returning list of companies corresponding to a search criteria, let us userID.
CompanyList list1 = CompanyList .GetList(userID1);
CompanyList list2 = CompanyList .GetList(userID2);
CompanyList list3 = CompanyList .GetList(userID3);
CompanyList list4 = CompanyList .GetList(userID4);
CompanyList list5 = CompanyList .GetList(userID5);
The solution I implemented is (worked well):
CompanyList _finalList = list1;
*foreach (CompanyList _list in {_list2 ,_list3 ,_list4 ,_list5}) //loop thorugh all other list
{
foreach (Company item in _list)
{
for (int i = 0; i <= _finalList.Count - 1; i++)
{
if (_finalList.Item(i).CompanyNo== item.CompanyNo)
//***EXIT TAKE NEXT LIST - GO TO *
}
if (i == _finalList.Count - 1) //else check end of first list
{
//company no. not yet encountered(new)
int pos = 0;
foreach (Company companyInfo in _finalList) //search for position for new company no.
{
if (companyInfo.CompanyNo> item.CompanyNo)
{
break;
}
else
{
pos = pos + 1; //increment position
}
}
_finalList.Insert(pos, item); 'Add new item
}
}
}
**the code is converted from VB.Net to C#. Here I could not find the quivalent code piece for this line so replaced it with the concept.
I am not an expert C# programmer and just wondering if there is any better or simpler way to do this?
Data example:
Input:
list1[0] = {0,"TCS"};
list1[1] = {1,"Infosys"};
list2[0] = {8,"IBM"};
list3[1] = {1,"Infosys"};
list4[0] = {0,"TCS"};
list5[0] = {9,"Accenture"};
list5[1] = {6,"HCL"};
Output:
finalList[0] = {0,"TCS"};
finalList[1] = {1,"Infosys"};
finalList[2] = {6,"HCL"};
finalList[3] = {8,"IBM"};
finalList[4] = {9,"Accenture"};
Regards
Sj
Okay, so you have a number of sequences of something, in your case "something" would be Company, which doesn't overide object.Equals or object.HashCode.
So, a new extension like this, might prove useful
public static IEnumerable<T> Union(
this IEnumerable<T> source,
IEqualityComparer<T> comparer,
params IEnumerable<T>[] others)
{
if (comparer == null)
{
comparer = EqualityComparer<T>.Default;
}
var result = source.Distinct(comparer);
foreach(var o in source)
{
if (o == null)
{
continue;
}
result = result.Union(o, comparer);
}
return result;
}
To make this, and other functions that take an IEqualityComparer simple to use, you could add this class to your code,
public class EqualityComparerImproved<T> : EqaulityComparer<T>
{
private readonly Func<T, T> equalityComparison;
private readonly Func<T, int> hashGenerator;
private EqualityComparerImproved(
Func<T, T> equalityComparison,
Func<T, int> hashGenerator)
{
this.equalityComparison = equalityComparison;
this.hashGenerator = hashGenerator;
}
public static EqualityComparerImproved<T> Create
Func<T, T> equalityComparison,
Func<T, int> hashGenerator)
{
if (equalityComparison == null)
{
throw new ArgumentNullException("equalityComparison");
}
if (hashGenerator == null)
{
throw new ArgumentNullException("hashGenerator");
}
return new EqualityComparerImproved<T>(
equalityComparison,
hashGenerator);
}
public override bool Equals(T x, T y)
{
return this.equalityComparison(x, y);
}
public override int GetHashCode(T obj)
{
return this.hashGenerator(obj);
}
}
Once these two, admittedly lengthy, bits of code were in place you could do
var output = list1.Union(
EqualityComparerImproved<Company>.Create(
(x, y) => x.companyNo == y.companyNo && x.Name == y.Name,
(obj) =>
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + obj.companyNo;
hash = hash * 23 + obj.Name.GetHashCode();
return hash;
}
},
list2,
list3,
list4,
list5);
or if companyNo is a unique key,
var output = list1.Union(
EqualityComparerImproved<Company>.Create(
(x, y) => x.companyNo == y.companyNo,
(obj) => obj.companyNo),
list2,
list3,
list4,
list5);
would suffice.
Similar to Habib's solution, but a bit more concise and complete.
int[] userIDs = new[] { userID1, userID2, userID3, userID4, userID5 };
IEnumerable<Company> distinctCompanies =
from companyList in userIDs.Select(CompanyList.GetList)
from company in companyList
group company by company.companyNo into companiesWithSameNo
select companiesWithSameNo.First();
CompanyList finalList = new CompanyList();
finalList.AddRange(distinctCompanies);
You might have a constructor in CompanyList that directly accepts an IEnumerable<Company>, too, so you could directly pass distinctCompanies there instead.
You can use either GroupBy or Union to remove duplicates... Union makes for a little cleaner linq (I think) but either can work... the downside is that you also need a custom IEqualityComparer in this case since equals on your company objects will return false (since they are different instances)... An alternative is to have your Company class implement IEqualityComparer and just copy the code I have implementing that interface into your Company class.
// Union gives you a unique list if it knows how to compare the objects properly
var companyEqualityComparer = new CompanyEqualityComparer();
foreach (var companyList in new List<List<Company>>(){list2, list3, list4, list5})
{
combinedList = combinedList.Union(companyList, companyEqualityComparer);
}
// Order your output list
var finalList = combinedList.OrderBy(c => c.companyNo).ToList();
Define your CompanyEqualityComparer...
// CompanyEqualityComparer which is needed since your companies are different instances
public class CompanyEqualityComparer : IEqualityComparer<Company>
{
public bool Equals(Company x, Company y)
{
return x.companyNo.Equals(y.companyNo);
}
public int GetHashCode(Company obj)
{
return obj.companyNo.GetHashCode();
}
}
I think you need something like:
List<Company> inputList = //Get your input List
List<Company> outputList = inputList.GroupBy(r => r.companyNo)
.Select(grp => new Company
{
companyNo = grp.Key,
Name = grp.First().Name,
})
.OrderBy(r=> r.companyNo)
.ToList();

Sort a List<T> by enum where enum is out of order

I have a List of messages.
Each message has a type.
public enum MessageType
{
Foo = 0,
Bar = 1,
Boo = 2,
Doo = 3
}
The enum names are arbitrary and cannot be changed.
I need to return the list sorted as: Boo, Bar, Foo, Doo
My current solution is to create a tempList, add the values in the order I want, return the new list.
List<Message> tempList = new List<Message>();
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Boo));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Bar));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Foo));
tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Doo));
messageList = tempList;
How can I do this with an IComparer?
An alternative to using IComparer would be to build an ordering dictionary.
var orderMap = new Dictionary<MessageType, int>() {
{ MessageType.Boo, 0 },
{ MessageType.Bar, 1 },
{ MessageType.Foo, 2 },
{ MessageType.Doo, 3 }
};
var orderedList = messageList.OrderBy(m => orderMap[m.MessageType]);
So, let's write our own comparer:
public class MyMessageComparer : IComparer<MessageType> {
protected IList<MessageType> orderedTypes {get; set;}
public MyMessageComparer() {
// you can reorder it's all as you want
orderedTypes = new List<MessageType>() {
MessageType.Boo,
MessageType.Bar,
MessageType.Foo,
MessageType.Doo,
};
}
public int Compare(MessageType x, MessageType y) {
var xIndex = orderedTypes.IndexOf(x);
var yIndex = orderedTypes.IndexOf(y);
return xIndex.CompareTo(yIndex);
}
};
How to use:
messages.OrderBy(m => m.MessageType, new MyMessageComparer())
There is a easier way: just create ordereTypes list and use another overload of OrderBy:
var orderedTypes = new List<MessageType>() {
MessageType.Boo,
MessageType.Bar,
MessageType.Foo,
MessageType.Doo,
};
messages.OrderBy(m => orderedTypes.IndexOf(m.MessageType)).ToList();
Hm.. Let's try to take advantages from writing our own IComparer. Idea: write it like our last example but in some other semantic. Like this:
messages.OrderBy(
m => m.MessageType,
new EnumComparer<MessageType>() {
MessageType.Boo,
MessageType.Foo }
);
Or this:
messages.OrderBy(m => m.MessageType, EnumComparer<MessageType>());
Okay, so what we need. Our own comparer:
Must accept enum as generic type (how to solve)
Must be usable with collection initializer syntax (how to)
Must sort by default order, when we have no enum values in our comparer (or some enum values aren't in our comparer)
So, here is the code:
public class EnumComparer<TEnum>: IComparer<TEnum>, IEnumerable<TEnum> where TEnum: struct, IConvertible {
protected static IList<TEnum> TypicalValues { get; set; }
protected IList<TEnum> _reorderedValues;
protected IList<TEnum> ReorderedValues {
get { return _reorderedValues.Any() ? _reorderedValues : TypicalValues; }
set { _reorderedValues = value; }
}
static EnumComparer() {
if (!typeof(TEnum).IsEnum)
{
throw new ArgumentException("T must be an enumerated type");
}
TypicalValues = new List<TEnum>();
foreach (TEnum value in Enum.GetValues(typeof(TEnum))) {
TypicalValues.Add(value);
};
}
public EnumComparer(IList<TEnum> reorderedValues = null) {
if (_reorderedValues == null ) {
_reorderedValues = new List<TEnum>();
return;
}
_reorderedValues = reorderedValues;
}
public void Add(TEnum value) {
if (_reorderedValues.Contains(value))
return;
_reorderedValues.Add(value);
}
public int Compare(TEnum x, TEnum y) {
var xIndex = ReorderedValues.IndexOf(x);
var yIndex = ReorderedValues.IndexOf(y);
// no such enums in our order list:
// so this enum values must be in the end
// and must be ordered between themselves by default
if (xIndex == -1) {
if (yIndex == -1) {
xIndex = TypicalValues.IndexOf(x);
yIndex = TypicalValues.IndexOf(y);
return xIndex.CompareTo(yIndex);
}
return -1;
}
if (yIndex == -1) {
return -1; //
}
return xIndex.CompareTo(yIndex);
}
public void Clear() {
_reorderedValues = new List<TEnum>();
}
private IEnumerable<TEnum> GetEnumerable() {
return Enumerable.Concat(
ReorderedValues,
TypicalValues.Where(v => !ReorderedValues.Contains(v))
);
}
public IEnumerator<TEnum> GetEnumerator() {
return GetEnumerable().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerable().GetEnumerator();
}
}
So, well, let's make sorting more faster. We need to override default OrderBy method for our enums:
public static class LinqEnumExtensions
{
public static IEnumerable<TSource> OrderBy<TSource, TEnum>(this IEnumerable<TSource> source, Func<TSource, TEnum> selector, EnumComparer<TEnum> enumComparer) where TEnum : struct, IConvertible
{
foreach (var enumValue in enumComparer)
{
foreach (var sourceElement in source.Where(item => selector(item).Equals(enumValue)))
{
yield return sourceElement;
}
}
}
}
Yeah, that's lazy. You can google how yield works. Well, let's test speed. Simple benchmark: http://pastebin.com/P8qaU20Y. Result for n = 1000000;
Enumerable orderBy, elementAt: 00:00:04.5485845
Own orderBy, elementAt: 00:00:00.0040010
Enumerable orderBy, full sort: 00:00:04.6685977
Own orderBy, full sort: 00:00:00.4540575
We see, that our own orderBy by is more lazy that standart order by (yeah, it doesn't need to sort everything). And faster even for fullsort.
Problems in this code: it doesn't support ThenBy(). If you need this, you can write your own linq extension that returns IOrderedEnumerable There are a blog post series by Jon Skeet which goes into LINQ to Objects in some depth, providing a complete alternative implementation. The basis of IOrderedEnumerable is covered in part 26a and 26b, with more details and optimization in 26c and 26d.
Instead of using an IComparer, you could also use a SelectMany approach, which should have better performance for large message lists, if you have a fixed number of message types.
var messageTypeOrder = new [] {
MessageType.Boo,
MessageType.Bar,
MessageType.Foo,
MessageType.Doo,
};
List<Message> tempList = messageTypeOrder
.SelectMany(type => messageList.Where(m => m.MessageType == type))
.ToList();
You may avoid writing a completely new type just to implement IComparable. Use the Comparer class instead:
IComparer<Message> comparer = Comparer.Create<Message>((message) =>
{
// lambda that compares things
});
tempList.Sort(comparer);
You can build a mapping dictionary dynamically from the Enum values with LINQ like this:
var mappingDIctionary = new List<string>((string[])Enum.GetNames(typeof(Hexside)))
.OrderBy(label => label )
.Select((i,n) => new {Index=i, Label=n}).ToList();
Now any new values added to the Enum n future will automatically get properly mapped.
Also, if someone decides to renumber, refactor, or reorder the enumeration, everything is handled automatically.
Update:
As pointed out below, Alphabetical ordering was not called for; rather a semi- alphabetical ordering, so essentially random. Although not an answer to this particular question, this technique might be useful to future visitors, so I will leave it standing.
No need to have the mapping. This should give you the list and order based on the enum. You don't have to modify anything even when you change the enum's order or and new items...
var result = (from x in tempList
join y in Enum.GetValues(typeof(MessageType)).Cast<MessageType>()
on x equals y
orderby y
select y).ToList();
If you are about to get this working with Entity Framework (EF), you would have to spread out your enum in your OrderBy as such:
messageList.OrderBy(m =>
m.MessageType == MessageType.Boo ? 0 :
m.MessageType == MessageType.Bar ? 1 :
m.MessageType == MessageType.Foo ? 2 :
m.MessageType == MessageType.Doo ? 3 : 4
);
This creates a sub select with CASE WHEN, then ORDER BY on that temporary column.

C#: how to define an extension method as "with" in F#?

F# has a convenient feature "with", example:
type Product = { Name:string; Price:int };;
let p = { Name="Test"; Price=42; };;
let p2 = { p with Name="Test2" };;
F# created keyword "with" as the record types are by default immutable.
Now, is it possible to define a similar extension in C#?
seems it's a bit tricky, as in C# i'm not sure how to convert a string
Name="Test2"
to a delegate or expression?
public static T With<T, U>(this T obj, Expression<Func<T, U>> property, U value)
where T : ICloneable {
if (obj == null)
throw new ArgumentNullException("obj");
if (property == null)
throw new ArgumentNullException("property");
var memExpr = property.Body as MemberExpression;
if (memExpr == null || !(memExpr.Member is PropertyInfo))
throw new ArgumentException("Must refer to a property", "property");
var copy = (T)obj.Clone();
var propInfo = (PropertyInfo)memExpr.Member;
propInfo.SetValue(copy, value, null);
return copy;
}
public class Foo : ICloneable {
public int Id { get; set; }
public string Bar { get; set; }
object ICloneable.Clone() {
return new Foo { Id = this.Id, Bar = this.Bar };
}
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = foo.With(x => x.Bar, "boo-ya");
Console.WriteLine(newFoo.Bar); //boo-ya
}
Or, using a copy constructor:
public class Foo {
public Foo(Foo other) {
this.Id = other.Id;
this.Bar = other.Bar;
}
public Foo() { }
public int Id { get; set; }
public string Bar { get; set; }
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = new Foo(foo) { Bar = "boo-ya" };
Console.WriteLine(newFoo.Bar);
}
And a slight variation on George's excellent suggestion, that allows for multiple assignments:
public static T With<T>(this T obj, params Action<T>[] assignments)
where T : ICloneable {
if (obj == null)
throw new ArgumentNullException("obj");
if (assignments == null)
throw new ArgumentNullException("assignments");
var copy = (T)obj.Clone();
foreach (var a in assignments) {
a(copy);
}
return copy;
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = foo.With(x => x.Id = 2, x => x.Bar = "boo-ya");
Console.WriteLine(newFoo.Bar);
}
I would probably use the second one since (1) any general purpose solution is going to be unnecessarily slow and convoluted; (2) it has the closest syntax to what you want (and the syntax does what you expect); (3) F# copy-and-update expressions are implemented similarly.
Maybe something like this:
void Main()
{
var NewProduct = ExistingProduct.With(P => P.Name = "Test2");
}
// Define other methods and classes here
public static class Extensions
{
public T With<T>(this T Instance, Action<T> Act) where T : ICloneable
{
var Result = Instance.Clone();
Act(Result);
return Result;
}
}
As an alternative to lambda function, you can use parameters with default values. The only minor issue is that you have to pick some default value that means do not change this parameter (for reference types), but null should be a safe choice:
class Product {
public string Name { get; private set; }
public int Price { get; private set; }
public Product(string name, int price) {
Name = name; Price = price;
}
// Creates a new product using the current values and changing
// the values of the specified arguments to a new value
public Product With(string name = null, int? price = null) {
return new Product(name ?? Name, price ?? Price);
}
}
// Then you can write:
var prod2 = prod1.With(name = "New product");
You have to define the method yourself, but that's always the case (unless you're going to use reflection, which less efficient). I think the syntax is reasonably nice too. If you want to make it as nice as in F#, then you'll have to use F# :-)
There is no native ability to do this in C# short of an extension method, but at what cost? a and b are reference types and any suggestion that b is based ("with") on a causes immediate confusion as to how many objects we are working with. Is there only one? Is b a copy of a ? Does b point to a ?
C# is not F#.
Please see a previous SO question of mine as answered by Eric Lippert:
"Amongst my rules of thumb for writing clear code is: put all side effects in statements; non-statement expressions should have no side effects."
More fluent C# / .NET

Anonymous type and intersection of 2 lists

public class thing
{
public int Id{get;set;}
public decimal shouldMatch1 {get;set;}
public int otherMatch2{get;set;}
public string doesntMatter{get;set;}
public int someotherdoesntMatter{get;set;}
}
List<thing> firstList = new List<thing>();
List<thing> secondList = new List<thing>();
firstList.Add( new thing{ Id=1,shouldMatch1 = 1.11M, otherMatch2=1000,doesntMatter="Some fancy string", someotherdoesntMatter=75868});
firstList.Add( new thing{ Id=2,shouldMatch1 = 2.22M, otherMatch2=2000,doesntMatter="Some fancy string", someotherdoesntMatter=65345});
firstList.Add( new thing{ Id=3,shouldMatch1 = 3.33M, otherMatch2=3000,doesntMatter="Some fancy string", someotherdoesntMatter=75998});
firstList.Add( new thing{ Id=4,shouldMatch1 = 4.44M, otherMatch2=4000,doesntMatter="Some fancy string", someotherdoesntMatter=12345});
secondList.Add( new thing{ Id=100,shouldMatch1 = 1.11M, otherMatch2=1000,doesntMatter="Some fancy string", someotherdoesntMatter=75868});
secondList.Add( new thing{ Id=200,shouldMatch1 = 2.22M, otherMatch2=200,doesntMatter="Some fancy string", someotherdoesntMatter=65345});
secondList.Add( new thing{ Id=300,shouldMatch1 = 3.33M, otherMatch2=300,doesntMatter="Some fancy string", someotherdoesntMatter=75998});
secondList.Add( new thing{ Id=400,shouldMatch1 = 4.44M, otherMatch2=4000,doesntMatter="Some fancy string", someotherdoesntMatter=12345});
//Select new firstList.Id,secondList.Id where firstList.shouldMatch1 ==secondList.shouldMatch1 && firstList.otherMatch2==secondList.otherMatch2
//SHould return
//1,100
//4,400
Is there a way to intersect the lists, or must I iterate them?
Pseudocode
firstList.Intersect(secondList).Where(firstList.shouldMatch1 == secondList.shouldMatch1 && firstList.otherMatch2 == secondList.otherMatch2)
Select new {Id1=firstList.Id,Id2=secondList.Id};
Regards
_Eric
You could use an approach other than intersecting and implementing an IEqualityComparer, as follows:
var query = from f in firstList
from s in secondList
where f.shouldMatch1 == s.shouldMatch1 &&
f.otherMatch2 == s.otherMatch2
select new { FirstId = f.Id, SecondId = s.Id };
foreach (var item in query)
Console.WriteLine("{0}, {1}", item.FirstId, item.SecondId);
This is essentially the Enumerable.SelectMany method in query format. A join would likely be quicker than this approach.
Consider using a multi-condition join to join your records. An intersect would cause you to lose ID's either on the left or the right.
Here is an example of a working multi-column join for this particular scenario. The appeal of this query is that it requires no equality comparer, and it allows you to retrieve the ID column while joining on the other specified columns.
var query = from first in firstList
join second in secondList on
new { first.shouldMatch1, first.otherMatch2 }
equals
new { second.shouldMatch1, second.otherMatch2 }
select new
{
FirstId = first.Id,
SecondId = second.Id
};
You need to make your thing type override Equals and GetHashCode to indicate its equality semantics:
public sealed class Thing : IEquatable<Thing>
{
public int Id{get;set;}
public decimal ShouldMatch1 {get;set;}
public int OtherMatch2{get;set;}
public string DoesntMatter{get;set;}
public int SomeOtherDoesntMatter{get;set;}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 31 + ShouldMatch1.GetHashCode() ;
hash = hash * 31 + OtherMatch2.GetHashCode() ;
return hash;
}
public override bool Equals(object other) {
return Equals(other as Thing);
}
public bool Equals(Thing other) {
if (other == null) {
return false;
}
return ShouldMatch1 == other.ShouldMatch1 &&
OtherMatch2 == other.OtherMatch2;
}
}
Note that sealing the class makes the equality test simpler. Also note that if you put one of these in a dictionary as a key but then change Id, ShouldMatch1 or OtherMatch2 you won't be able to find it again...
Now if you're using a real anonymous type, you don't get to do this... and it's tricky to implement an IEqualityComparer<T> to pass to Intersect when it's anonymous. You could write an IntersectBy method, a bit like MoreLINQ's DisinctBy method... that's probably the cleanest approach if you're really using an anonymous type.
You'd use it like this:
var query = first.Intersect(second);
You then end up with an IEnumerable<Thing> which you can get the right bits out of.
Another option is to use a join:
var query = from left in first
join right in second
on new { left.ShouldMatch1, left.OtherMatch2 } equals
new { right.ShouldMatch1, right.OtherMatch2 }
select new { left, right };
(EDIT: I've just noticed others have done a join too... ah well.)
Yet another option if you're only interested in the bits of the match is to project the sequences:
var query = first.Select(x => new { x.ShouldMatch1, x.OtherMatch2 })
.Intersect(second.Select(x => new { x.ShouldMatch1,
x.OtherMatch2 }));
You will need an equality comparer:
public class thingEqualityComparer : IEqualityComparer<thing>
{
#region IEqualityComparer<thing> Members
public bool Equals(thing x, thing y) {
return (x.shouldMatch1 == y.shouldMatch1 && x.otherMatch2 == y.otherMatch2)
public int GetHashCode(thing obj) {
// if this does not suffice provide a better implementation.
return obj.GetHashCode();
}
#endregion
}
Then you can intersect the collections with:
firstList.Intersect(secondList, new thingEqualityComparer());
Alternatively, you can override the Equal function (see John's solution).
Also please not that thing is not anonymous class - this would be for example new { prop = 1 }.

C# Generic List Union Question

I'm trying to merge 2 lists using "Union" so I get rid of duplicates. Following is the sample code:
public class SomeDetail
{
public string SomeValue1 { get; set; }
public string SomeValue2 { get; set; }
public string SomeDate { get; set; }
}
public class SomeDetailComparer : IEqualityComparer<SomeDetail>
{
bool IEqualityComparer<SomeDetail>.Equals(SomeDetail x, SomeDetail y)
{
// Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y))
return true;
// Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.SomeValue1 == y.SomeValue1 && x.SomeValue2 == y.SomeValue2;
}
int IEqualityComparer<SomeDetail>.GetHashCode(SomeDetail obj)
{
return obj.SomeValue1.GetHashCode();
}
}
List<SomeDetail> tempList1 = new List<SomeDetail>();
List<SomeDetail> tempList2 = new List<SomeDetail>();
List<SomeDetail> detailList = tempList1.Union(tempList2, SomeDetailComparer).ToList();
Now the question is can I use Union and still get the record which has the latest date (using SomeDate property). The record itself can either be in tempList1 or tempList2.
Thanks in advance
The operation that is really suited to this purpose is an full outer join. The Enumerable class has an implementation of inner join, which you can use to find the duplicates and select whichever you prefer.
var duplicates = Enumerable.Join(tempList1, tempList2, keySelector, keySelector,
(item1, item2) => (item1.SomeDate > item2.SomeDate) ? item1 : item2)
.ToList();
keySelector is simply a function (could be a lambda expression) that extracts a key from an object of type SomeDetail. Now, to implement the full outer join, try something like this:
var keyComparer = (SomeDetail item) => new { Value1 = item.SomeValue1,
Value2 = item.SomeDetail2 };
var detailList = Enumerable.Union(tempList1.Except(tempList2, equalityComparer),
tempList2.Except(tempList1, equalityComparer)).Union(
Enumerable.Join(tempList1, tempList2, keyComparer, keyComparer
(item1, item2) => (item1.SomeDate > item2.SomeDate) ? item1 : item2))
.ToList();
equalityComparer should be an object that implements IEqualityComparer<SomeDetail> and effectively uses the keyComparer function for testing equality.
Let me know if that does the job for you.
You'd have to be able to tell Union how to pick which one of the duplicates to use. I don't know of a way to do that other than writing your own Union.
You cannot with the standard Union method, but you can create an extension method Union for List<SomeDetail> with this special handling and this method will be used because the signature fits better.
Why not just use HashSet<T>?
List<SomeDetail> tempList1 = new List<SomeDetail>();
List<SomeDetail> tempList2 = new List<SomeDetail>();
HashSet<SomeDetail> hs = new HashSet<SomeDetail>(new SomeDetailComparer());
hs.UnionWith(tempList1);
hs.UnionWith(tempList2);
List<SomeDetail> detailList = hs.ToList();
Merge generic lists
public static List<T> MergeListCollections<T>(List<T> firstList, List<T> secondList)
{
List<T> merged = new List<T>(firstList);
merged.AddRange(secondList);
return merged;
}
try this:
list1.RemoveAll(p => list2.Any(z => z.SomeValue1 == p.SomeValue1 &&
z => z.SomeValue2 == p.SomeValue1 &&
z => z.SomeDate == p.SomeDate));
var list3 = list2.Concat<SomeDetail>(list1).ToList();

Categories

Resources