-3

I have two lists of the same object and I want to find the Union and Intersection of these lists based on a case-insensitive comparison of a property of the object.

For simplicity, let's call it a Person, and I want to filter on the Person.Name property.

What is the recommended way to do this? I'm hoping to keep the code in a single line of Linq.

Currently I'm doing the following:

public class Person { public string Name { get; set; } }

-

var people =
    firstListOfPeople.Where(
        p1 => p1.Name != null &&
            secondListOfPeople
                .Where(p2 => p2.Name != null)
                .Select(p2 => p2.Name.ToUpper())
                .Contains(p1.Name.ToUpper()));
Rufus L
  • 36,127
  • 5
  • 30
  • 43
  • 1
    It seems like the `Select.Contains` is a little over the top; but otherwise the code seems reasonable. Is that code causing a problem? – BradleyDotNET Nov 05 '14 at 00:06
  • For equals - http://stackoverflow.com/questions/3121957/how-can-i-do-a-case-insensitive-string-comparison, for Contains - IndexOf: http://stackoverflow.com/questions/444798/case-insensitive-containsstring ... Can't make duplicate of both :) – Alexei Levenkov Nov 05 '14 at 00:06
  • @BradleyDotNET No, the code works. Just hoping for something simpler (even if it meant extending the Person clase), like: `firstListOfPeople.Intersect(secondListOfPeople)` – Rufus L Nov 05 '14 at 00:08
  • @AlexeiLevenkov Thanks, I know about that...but I don't see how to implement that in the Linq statement. – Rufus L Nov 05 '14 at 00:10
  • 2
    Or if you are looking for set operations - custom comparer - http://stackoverflow.com/questions/4340273/intersect-with-a-custom-iequalitycomparer-using-linq – Alexei Levenkov Nov 05 '14 at 00:10
  • I guess I'm not getting how your desired code looks like - it is already "one statement"... And title of the post feels somewhat unrelated to what I think you are looking for. – Alexei Levenkov Nov 05 '14 at 00:11

2 Answers2

4

You can collapse your code down to this:

firstListOfPeople.Intersect(secondListOfPeople);

The catch comes with the case-insensitive compare of the name. Intersect uses the default equality comparer (reference equality), so you need to implement IEqualityComparer<T> (MSDN).

That comparison would do the name based comparison. You would then create one and pass it to the correct overload of Intersect: http://msdn.microsoft.com/en-us/library/vstudio/bb355408(v=vs.100).aspx

firstListOfPeople.Instersect(secondListOfPeople, myComparer);
BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
  • Great, I think this is on the right track. Unfortunately the class already has a custom comparer that does more than what I need (It does a deeper comparison, for example on the `Age` property as well). But it sounds like I can pass in a different one. Let me try that out. – Rufus L Nov 05 '14 at 00:16
  • @PeterDuniho, can you use a `Comparer` for `Intersect()`? I don't think `Comparer` is a `IEqualityComparer`, therefore it doesn't match the signature of `Intersect()` (second overload). – devuxer Nov 05 '14 at 01:09
  • @devuxer: you're right, I forgot `Comparer` only implements `IComparer`, not `IEqualityComparer` (in hindsight, it makes perfect sense that it wouldn't). Without the hash-code implementation, it's not useful here. (But it does seem to me that .NET ought to have an `EqualityComparer.Create()` method :) ). – Peter Duniho Nov 05 '14 at 01:22
1

I think @BradleyDotNET has the right answer, but since I already had an example mostly complete, I thought I'd post it in case it helps someone down the road:

void Main()
{
    var firstListOfPeople = new[]
    {
        new Person { Name = "Rufus" },
        new Person { Name = "Bob" },
        new Person { Name = "steve" },
    };

    var secondListOfPeople = new[]
    {
        new Person { Name = "john" },
        new Person { Name = "Bob" },
        new Person { Name = "rufus" },
    };

    var people = firstListOfPeople.Intersect(secondListOfPeople, new PersonNameComparer());

    people.Dump(); // displays the result if you are using LINQPad
}

public class Person
{
    public string Name { get; set; }
}

public class PersonNameComparer: EqualityComparer<Person>
{

    public override bool Equals(Person p1, Person p2)
    {
        return p1.Name.Equals(p2.Name, StringComparison.OrdinalIgnoreCase);
    }

    public override int GetHashCode(Person p)
    {
        return p.Name.ToLower().GetHashCode();
    }
}
devuxer
  • 41,681
  • 47
  • 180
  • 292