0

I am having a bit of a frustrating time finding a simple method to compare and prove that the contents of two lists are equal. I have looked at a number of solutions on stackoverflow but I have not been successful. Some of the solutions look like they will require a large amount of work to implement and do something that on the face of it to my mind should be simpler, but perhaps I am too simple to realize that this cannot be done simply :)

I have created a fiddle with some detail that can be viewed here: https://dotnetfiddle.net/cvQr5d

Alternatively please find the full example below, I am having trouble with the object comparison method (variable finalResult) as it's returning false and if the content were being compared I would expect the value to be true:

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

public class ResponseExample
{
    public Guid Id { get; set; } = Guid.Parse("00000000-0000-0000-0000-000000000000");
    public int Value { get; set; } = 0;
    public string Initials { get; set; } = "J";
    public string FirstName { get; set; } = "Joe";
    public string Surname { get; set; } = "Blogs";
    public string CellPhone { get; set; } = "0923232199";
    public bool EmailVerified { get; set; } = false;
    public bool CellPhoneVerified { get;  set; } = true;
}

public class Program
{
    public static void Main()
    {
        var responseOne = new ResponseExample();
        var responseTwo = new ResponseExample();
        var responseThree = new ResponseExample();
        var responseFour = new ResponseExample();
        
        List<ResponseExample> objectListOne = new List<ResponseExample>();
        objectListOne.Add(responseOne);
        objectListOne.Add(responseTwo);
        
        List<ResponseExample> objectListTwo = new List<ResponseExample>();
        objectListTwo.Add(responseThree);
        objectListTwo.Add(responseFour);

        bool result = objectListOne.Count == objectListTwo.Count();
        Console.WriteLine($"Count: {result}");
        bool finalResult = ScrambledEquals<ResponseExample>(objectListOne, objectListTwo);
        Console.WriteLine($"Object compare: {finalResult}");
    }
    
    //https://stackoverflow.com/a/3670089/3324415
    public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
    {
        var cnt = new Dictionary<T,
          int>();
        foreach (T s in list1)
        {
            if (cnt.ContainsKey(s))
            {
                cnt[s]++;
            }
            else
            {
                cnt.Add(s, 1);
            }
        }
        foreach (T s in list2)
        {
            if (cnt.ContainsKey(s))
            {
                cnt[s]--;
            }
            else
            {
                return false;
            }
        }
        return cnt.Values.All(c => c == 0);
    }
}
BernardV
  • 640
  • 10
  • 28
  • Your code would work for a list of primitives like ints but with objects it wont compare the contents of the object to find they are the same. It will say they are different objects which they are. (responseOne == responseTwo) will return false. – Kristian Fitzgerald Aug 11 '21 at 17:09
  • You should implement an equalitycomparer class for your objects by implementing the `IEqualityComparer` interface or deriving from [EqualityComparer](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1?view=net-5.0) class (have a look at the example there). In this class you define, when two of your objects are considered to be equal. Without a comparer you would compare references which in your case are all different. Then you pass this comparer in SequenceEquals method suggested by 500 - Internal Server Error. – Steeeve Aug 11 '21 at 17:18

4 Answers4

1

As people in comments have pointed out this will not work as comparing a complex type by default compares whether the reference is the same. Field by field comparison will not work without implementing equality methods (and then you would need to overload GetHashCode and so on). See https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=net-5.0

However, if you can use c# 9, which is what you have in the fiddle you can define the type as a record instead of class. Records have built in field by field comparison. See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#characteristics-of-records

So public class ResponseExample would become public record ResponseExample and your code works as you expect.

John Sloper
  • 1,813
  • 12
  • 14
  • Thank you @John Sloper this is what I was looking for! I tested your theory around `public record ResponseExample` and it seems to be working perfectly. When I set a different value to one of the response values example `responseFour.FirstName = "Peter";` is does fail as I would expect it to. – BernardV Aug 12 '21 at 05:32
0

Use Enumerable.All<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>) Method which Determines whether all elements of a sequence satisfy a condition.

Once you have initilized your two List

list1.All(x=>list2.Contains(x))

This works by ensuring that all elements in list2 are containted in list1 otherwise returns false

Daniel Kiptoon
  • 314
  • 1
  • 7
0

Your method as is will compare if the 2 lists contain the same objects. So it is returning false as there are 4 different objects. If you create your list like this, using the same objects, it will return true:

        List<ResponseExample> objectListOne = new List<ResponseExample>();
        objectListOne.Add(responseOne);
        objectListOne.Add(responseTwo);

        List<ResponseExample> objectListTwo = new List<ResponseExample>();
        objectListTwo.Add(responseTwo);
        objectListTwo.Add(responseOne);

To get a true value when the contents of the objects are the same you could serialize the objects into a json string like this:

    public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
    {
        JavaScriptSerializer json = new JavaScriptSerializer();

        var cnt = new Dictionary<string,
          int>();
        foreach (T _s in list1)
        {
            string s = json.Serialize(_s);
            if (cnt.ContainsKey(s))
            {
                cnt[s]++;
            }
            else
            {
                cnt.Add(s, 1);
            }
        }
        foreach (T _s in list2)
        {
            string s = json.Serialize(_s);
            if (cnt.ContainsKey(s))
            {
                cnt[s]--;
            }
            else
            {
                return false;
            }
        }
        return cnt.Values.All(c => c == 0);
    }
0

If the performance is not a big deal, you can use Newtonsoft.Json. We will be able to compare different types of objects as well as run a deep equals check.

First install the package:

Install-Package Newtonsoft.Json

Here is the code snip:

public static bool DeepEqualsUsingJson<T>(IList<T> l1, IList<T> l2)
{
    if (ReferenceEquals(l1, l2))
        return true;

    if (ReferenceEquals(l2, null))
        return false;

    if (l1.Count != l2.Count)
        return false;

    var l1JObject = l1.Select(i => JObject.FromObject(i)).ToList();
    var l2JObject = l2.Select(i => JObject.FromObject(i)).ToList();

    foreach (var o1 in l1JObject)
    {
        var index = l2JObject.FindIndex(o2 => JToken.DeepEquals(o1, o2));
        if (index == -1)
            return false;
        l2JObject.RemoveAt(index);
    }

    return l2JObject.Count == 0;
}
denys-vega
  • 3,522
  • 1
  • 19
  • 24