1

Given these two objects:

public class Foo{
   public string Result {get;set;}
   public List<Bar> Bars {get;set;}
}

public class Bar{
   public string Left {get;set;}
   public string Right {get;set;}
}

And instances of these looking like this:

List<Foo> myFoos = new List<Foo>()
        {
    new Foo { Bars = new List<Bar>
        {
            new Bar { Left = "myLeft1", Right = "myValue1"},
            new Bar { Left = "myLeft2", Right = "myValue2"}
        },
        Result = "TheWinningResult"},
    new Foo { Bars = new List<Bar>
        {
            new Bar { Left = "myLeft2", Right = "myValue2"},
            new Bar { Left = "myLeft3", Right = "myValue3"}
        },
        Result = "TheLosingResult"},
    new Foo{ Bars = new List<Bar>
        {
            new Bar { Left = "myLeft1", Right = "myValue1"},
            new Bar { Left = "myLeft2", Right = "myValue2"},
            new Bar { Left = "myLeft3", Right = "myValue3"}
        },
        Result = "TheOtherLosingResult"},
};

List<Bar> bars = new List<Bar>()
{
     new Bar{  Left = "myLeft1", Right = "myValue1" },
     new Bar{  Left = "myLeft2", Right = "myValue2" }
};

I am trying to find the FirstOrDefault() Foo where Foo.Bars has an exact matching bars

In this case, I am trying to return the Foo whos Result is "TheWinningResult"

I have tried the following:

Foo foo =  myFoos.Where(t => t.Bars.All(t2 => bars.Contains(t2))).FirstOrDefault();

Foo foo = myFoos.Where(t => bars.All(r => t.Bars.Contains(r))).FirstOrDefault();     

Foo foo =   myFoos.FirstOrDefault(t => t.Bars.Any(r => bars.All(ru => ru.Left == r.Left && ru.Right == r.Right)));

Any idea where I am going wrong?

Update

I forgot to mention, Order of the Bars within Foo should not matter.

Darren Wainwright
  • 30,247
  • 21
  • 76
  • 127

1 Answers1

4

The problem is in how contains compares the objects. By default comparison is done by reference equality. Here you want a different behavior - compare equality by content. To do so override Equals and GetHashCode of Bar:

public override bool Equals(object obj)
{
    var other = obj as Bar;
    if (obj == null)
        return false;
    return other.Left == Left && other.Right == Right;
}

public override int GetHashCode()
{
    unchecked
    {
        int hash = 17;
        hash = hash * 23 + Left.GetHashCode();
        hash = hash * 23 + Right.GetHashCode();
        return hash;
    }
}

And then retrieving the correct objects can be done by:

var result = myFoos.Where(item => bars.Count == item.Bars.Count && 
                                  !bars.Except(item.Bars).Any());

Or if using SequenceEqual:

var result = myFoos.Where(item => bars.SequenceEqual(item.Bars));

Another way to do it is by implementing the IEqualityComparer<Bar> and calling the matching Contains overload:

var result = myFoos.Where(t => t.Bars.All(t2 => bars.Contains(t2, new BarEqualityComparer())));

As for your third attempt the problem is that the All and Any is the other way round: you want for all bars in the foo to match to any of the items in the bars list:

Foo foo = myFoos.FirstOrDefault(t => t.Bars.All(r => bars.Any(ru => ru.Left == r.Left && ru.Right == r.Right)));

For what the override of the GetHashCode is for you can read these two:

  1. Object.GetHashCode
  2. Why is it important to override GetHashCode when Equals method is overridden?
Gilad Green
  • 36,708
  • 7
  • 61
  • 95