using System.Collections.Generic;
using System.Collections;
using static MoreLinq.MoreEnumerable;
public static void Main()
Console.WriteLine($"foo1 == foo2: {ValueEqualityComparer.ValueEquals(foo1, foo2)}");
Console.WriteLine($"foo1 == foo3: {foo1.ValueEquals(foo3)}");
Console.WriteLine($"foo3 == foo4: {foo3.ValueEquals(foo4)}");
Console.WriteLine($"bar1 == bar2: {bar1.ValueEquals(bar2)}");
Console.WriteLine($"bar1 == bar3: {bar1.ValueEquals(bar3)}");
Console.WriteLine($"bar3 == bar4: {bar3.ValueEquals(bar4)}");
Items = new List<string>();
public int Count { get; set; }
public List<string> Items { get; set; }
public Foo Inner { get; set; }
public string Message { get; set; }
public static class ValueEqualityComparer
public static bool ValueEquals<T>(this T self, T other) where T : class
var type = self.GetType();
if (type == typeof(string))
return self.Equals(other);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
var selfValue = property.GetValue(self);
var otherValue = property.GetValue(other);
if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string))
if (!selfValue.Equals(otherValue))
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
var selfList = ((IEnumerable)property.GetValue(self)).Cast<object>();
var otherList = ((IEnumerable)property.GetValue(other)).Cast<object>();
foreach (var element in selfList.EquiZip(otherList, (selfItem, otherItem) => new { selfItem, otherItem }))
if (!ValueEquals(element.selfItem, element.otherItem))
catch (InvalidOperationException)
if (!ValueEquals(selfValue, otherValue))