I want to override Dictionary's Equals method with return d1.Count == d2.Count && !d1.Except(d2).Any();
But I wanna do it for all the Dictionaries, and not just a specific type at a time.
I currently have a public static bool IsEqualTo(this Dictionary<string, byte> d1, Dictionary<string, byte> d2) method in my ExtensionMethods class, but it's neither generic (working with any KeyValuePairs), neither does it actually override the default "Equals" method.
Is there any way I can do this, or am I stuck to using what I currently have (perhaps I could at least make it a bit more generic with a template)?
To use your own custom ruls for Equality, you can implement IEqualityComparer<T> as CodeCaster suggested in the comments:
public sealed class DictionaryComparer<K, V> : IEqualityComparer<IDictionary<K, V>> {
public bool Equals(IDictionary<K, V>? left, IDictionary<K, V>? right) {
if (ReferenceEquals(left, right))
return true;
if (left is null || right is null)
return false;
return left.Count == right.Count && !left.Except(right).Any();
}
public int GetHashCode(IDictionary<K, V> value) =>
value is null ? 0 : value.Count;
}
Then whenever you want to compare two dictionaries:
Dictionary<int, string> dict1 = ...
Dictionary<int, string> dict2 = ...
if (new DictionaryComparer<int, string>().Equals(dict1, dict2)) {
//TODO: relevant code here
}
You can implement an extension method if you like:
public static class DictionaryExtensions {
public static bool EqualsToDictionary<K, V>(this IDictionary<K, V> left,
IDictionary<K, V> right) {
var comparer = new DictionaryComparer<K, V>();
return compare.Equals(left, right);
}
}
then
Dictionary<int, string> dict1 = ...
Dictionary<int, string> dict2 = ...
if (dict1.EqualsToDictionary(dict2)) {
//TODO: relevant code here
}
Why not simply make make your IsEqualTo method generic?
public static bool IsEqualTo<K, V>(this IDictionary<K, V> d1, IDictionary<K, V> d2)
{
return d1.Count == d2.Count && !d1.Except(d2).Any();
}
By extending IDictionary<TKey,TValue> instead of Dictionary<TKey,TValue> we can apply the extension method to other types of dictionaries like ConcurrentDictionary<TKey,TValue> or SortedDictionary<TKey,TValue> and many more.
Note that C# can infer the generic type parameters:
var d1 = new Dictionary<string, byte>();
var d2 = new Dictionary<string, byte>();
var s1 = new SortedDictionary<int, string>();
var s2 = new SortedDictionary<int, string>();
// Not necessary to specify the generic type parameters.
bool result1 = d1.IsEqualTo(d2);
bool result2 = s1.IsEqualTo(s2);
I have the following function:
public static Func<int[], int[], int> Foo()
{
Func<int[], int[], int> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
I would like to create same Func but for various number types (long, short, etc. and not only int).
The code below does not work. I receive the following error
(CS0019: Operator '*' cannot be applied to operands of type 'T' and 'T'):
public static Func<T[], T[], T> Foo<T>() where T : struct
{
Func<T[], T[], T> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
After some investigation I concluded that I need to generate code dynamically with expression trees, however, I could not find any useful resource on web. The ones I found only deal with very simple lambda expressions. I also tried to use reflection<> and ILSpy to peek into C#1 code automatically with the idea to manually change ints to Ts. However, it did not work - I think due to (RuntimeMethodHandle)/OpCode not supported: LdMemberToken/. Any Help would be appreciated. I am really interested in to solve this.
public static Expression<Func<int[], int[], int>> Foo()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(int[]), "first");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int[]), "second");
MethodInfo method = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/);
Expression[] array = new Expression[1];
MethodInfo method2 = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/);
Expression[] obj = new Expression[3] { parameterExpression, parameterExpression2, null };
ParameterExpression parameterExpression3 = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterExpression4 = Expression.Parameter(typeof(int), "y");
obj[2] = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(parameterExpression3, parameterExpression4), new ParameterExpression[2] { parameterExpression3, parameterExpression4 });
array[0] = Expression.Call(null, method2, obj);
return Expression.Lambda<Func<int[], int[], int>>(Expression.Call(null, method, array), new ParameterExpression[2] { parameterExpression, parameterExpression2 });
}
Generic math is an upcoming feature that is in preview right now. So in the future, "static abstract interface members" are the way to handle this. If you opt in for preview features, you can write valid C# code like this:
public static Func<T[], T[], T> Foo<T>()
where T :
unmanaged,
IMultiplyOperators<T, T, T>,
IAdditiveIdentity<T, T>,
IAdditionOperators<T, T, T>
{
Func<T[], T[], T> result =
static (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
// generic sum doesn't exist yet in linq
public static T Sum<T>(this IEnumerable<T> source)
where T :
unmanaged,
IAdditionOperators<T, T, T>,
IAdditiveIdentity<T, T>
{
T sum = T.AdditiveIdentity;
foreach (var item in source)
{
sum += item;
}
return sum;
}
It is still going to be some time before generic math releases and there are some unresolved problems with it (such as not being able to do "checked" math), so to actually answer your question, why not just use dynamic?
public static Func<T[], T[], T> Foo<T>() where T : struct
{
Func<T[], T[], T> result = (first, second) => DynamicDotProduct(first.Zip(second));
return result;
}
private static T DynamicDotProduct<T>(IEnumerable<(T first, T second)> zipped) where T : struct
{
// here I am assuming default(T) is zero of that type
dynamic sum = default(T);
foreach((dynamic x, T y) in zipped)
{
sum += x * y;
}
return sum;
}
If you are old school you can use Expressions to build up generic math
using System.Numerics;
internal class Program
{
static void Main(string[] args)
{
int[] i_a = { 1, 2, 3, 4 };
int[] i_b = { 7, 6, 5, 4 };
int i_dot = DotProduct(i_a, i_b);
// 50
float[] f_a = { 1f, 2f, 3f, 4f };
float[] f_b = { 7f, 6f, 5f, 4f };
float f_dot = DotProduct(f_a, f_b);
// 50f
Vector2[] v_a = { new Vector2(1, 2), new Vector2(3, 4) };
Vector2[] v_b = { new Vector2(7, 6), new Vector2(5, 4) };
Vector2 v_dot = DotProduct(v_a, v_b);
// [22f, 28f]
}
public static T DotProduct<T>(T[] left, T[] right)
{
if (left.Length==right.Length && left.Length>0)
{
// Use generic math defined in Operation<T>
T sum = Operation<T>.Mul(left[0], right[0]);
for (int i = 1; i < left.Length; i++)
{
sum = Operation<T>.Add(sum, Operation<T>.Mul(left[i], right[i]));
}
return sum;
}
return default(T);
}
}
public static class Operation<T>
{
static Operation()
{
var arg1 = Expression.Parameter(typeof(T));
var arg2 = Expression.Parameter(typeof(T));
Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(arg1, arg2), arg1, arg2).Compile();
Mul = Expression.Lambda<Func<T, T, T>>(Expression.Multiply(arg1, arg2), arg1, arg2).Compile();
}
///<summary>Generic Addition</summary>
public static Func<T, T, T> Add { get; }
///<summary>Generic Multiplication</summary>
public static Func<T, T, T> Mul { get; }
}
So any class that defines op_Addition and op_Multiplication, or the equivalent operators can be used with Operation<T>.Add and Operation<T>.Mul
Here System.Numerics.Vector2 defines the following operators, so you don't have to.
public static Vector2 operator +(Vector2 left, Vector2 right)
{
return new Vector2(left.X + right.X, left.Y + right.Y);
}
public static Vector2 operator *(Vector2 left, Vector2 right)
{
return new Vector2(left.X * right.X, left.Y * right.Y);
}
I want to compare in C# two dictionaries with as keys a string and as value a list of ints. I assume two dictionaries to be equal when they both have the same keys and for each key as value a list with the same integers (both not necessarily in the same order).
I use both the answers from this and this related question, but both fail my test suite for the test functions DoesOrderKeysMatter and DoesOrderValuesMatter.
My test suite:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Linq;
namespace UnitTestProject1
{
[TestClass]
public class ProvideReportTests
{
[TestMethod]
public void AreSameDictionariesEqual()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict1);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
}
[TestMethod]
public void AreDifferentDictionariesNotEqual()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
// act
bool dictsAreEqual = true;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsFalse(dictsAreEqual, "Dictionaries are equal");
}
[TestMethod]
public void DoesOrderKeysMatter()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
List<int> list3 = new List<int>();
list3.Add(3);
list3.Add(4);
dict2.Add("b", list3);
List<int> list4 = new List<int>();
list4.Add(1);
list4.Add(2);
dict2.Add("a", list4);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
}
[TestMethod]
public void DoesOrderValuesMatter()
{
// arrange
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
List<int> list1 = new List<int>();
list1.Add(1);
list1.Add(2);
dict1.Add("a", list1);
List<int> list2 = new List<int>();
list2.Add(3);
list2.Add(4);
dict1.Add("b", list2);
Dictionary<string, List<int>> dict2 = new Dictionary<string, List<int>>();
List<int> list3 = new List<int>();
list3.Add(2);
list3.Add(1);
dict2.Add("a", list3);
List<int> list4 = new List<int>();
list4.Add(4);
list4.Add(3);
dict2.Add("b", list4);
// act
bool dictsAreEqual = false;
dictsAreEqual = AreDictionariesEqual(dict1, dict2);
// assert
Assert.IsTrue(dictsAreEqual, "Dictionaries are not equal");
}
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
return dict1.Keys.Count == dict2.Keys.Count &&
dict1.Keys.All(k => dict2.ContainsKey(k) && object.Equals(dict2[k], dict1[k]));
// also fails:
// return dict1.OrderBy(kvp => kvp.Key).SequenceEqual(dict2.OrderBy(kvp => kvp.Key));
}
}
}
What is the correct way to compare these kind of dictionaries? Or is there an error in my (admittedly clumsily written) TestSuite?
Update
I'm trying to incorporate Servy's answer in my test suite, like below, but I get some errors (underlined with a red wiggly line in Visual Studio):
SetEquals in the `Equals method says: "does not contain a definition for SetEquals accepting a first argument of type Generic.List.
In AreDictionariesEqualit saysDictionaryComparer<List> is a type but is used as a variable.`
namespace UnitTestProject1
{
[TestClass]
public class ProvideReportTests
{
[TestMethod]
// ... same as above
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
DictionaryComparer<string, List<int>>(new ListComparer<int>() dc = new DictionaryComparer<string, List<int>>(new ListComparer<int>();
return dc.Equals(dict1, dict2);
}
}
public class DictionaryComparer<TKey, TValue> :
IEqualityComparer<Dictionary<TKey, TValue>>
{
private IEqualityComparer<TValue> valueComparer;
public DictionaryComparer(IEqualityComparer<TValue> valueComparer = null)
{
this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
public bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
if (x.Count != y.Count)
return false;
if (x.Keys.Except(y.Keys).Any())
return false;
if (y.Keys.Except(x.Keys).Any())
return false;
foreach (var pair in x)
if (!valueComparer.Equals(pair.Value, y[pair.Key]))
return false;
return true;
}
public int GetHashCode(Dictionary<TKey, TValue> obj)
{
throw new NotImplementedException();
}
}
public class ListComparer<T> : IEqualityComparer<List<T>>
{
private IEqualityComparer<T> valueComparer;
public ListComparer(IEqualityComparer<T> valueComparer = null)
{
this.valueComparer = valueComparer ?? EqualityComparer<T>.Default;
}
public bool Equals(List<T> x, List<T> y)
{
return x.SetEquals(y, valueComparer);
}
public int GetHashCode(List<T> obj)
{
throw new NotImplementedException();
}
}
public static bool SetEquals<T>(this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer)
{
return new HashSet<T>(second, comparer ?? EqualityComparer<T>.Default)
.SetEquals(first);
}
}
So first we need an equality comparer for dictionaries. It needs to ensure that they have matching keys and, if they do, compare the values of each key:
public class DictionaryComparer<TKey, TValue> :
IEqualityComparer<Dictionary<TKey, TValue>>
{
private IEqualityComparer<TValue> valueComparer;
public DictionaryComparer(IEqualityComparer<TValue> valueComparer = null)
{
this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
public bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
if (x.Count != y.Count)
return false;
if (x.Keys.Except(y.Keys).Any())
return false;
if (y.Keys.Except(x.Keys).Any())
return false;
foreach (var pair in x)
if (!valueComparer.Equals(pair.Value, y[pair.Key]))
return false;
return true;
}
public int GetHashCode(Dictionary<TKey, TValue> obj)
{
throw new NotImplementedException();
}
}
but this isn't enough on its own. We need to compare the values of the dictionary using another custom comparer, not the default comparer as the default list comparer won't look at the values of the list:
public class ListComparer<T> : IEqualityComparer<List<T>>
{
private IEqualityComparer<T> valueComparer;
public ListComparer(IEqualityComparer<T> valueComparer = null)
{
this.valueComparer = valueComparer ?? EqualityComparer<T>.Default;
}
public bool Equals(List<T> x, List<T> y)
{
return x.SetEquals(y, valueComparer);
}
public int GetHashCode(List<T> obj)
{
throw new NotImplementedException();
}
}
Which uses the following extension method:
public static bool SetEquals<T>(this IEnumerable<T> first, IEnumerable<T> second,
IEqualityComparer<T> comparer)
{
return new HashSet<T>(second, comparer ?? EqualityComparer<T>.Default)
.SetEquals(first);
}
Now we can simply write:
new DictionaryComparer<string, List<int>>(new ListComparer<int>())
.Equals(dict1, dict2);
I know this question already has an accepted answer, but I'd like to offer an even simpler alternative:
using System.Linq;
using System.Collections.Generic;
namespace Foo
{
public static class DictionaryExtensionMethods
{
public static bool ContentEquals<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, Dictionary<TKey, TValue> otherDictionary)
{
return (otherDictionary ?? new Dictionary<TKey, TValue>())
.OrderBy(kvp => kvp.Key)
.SequenceEqual((dictionary ?? new Dictionary<TKey, TValue>())
.OrderBy(kvp => kvp.Key));
}
}
}
Convert the dictionary to a KeyValuePair list and then compare as collections:
CollectionAssert.AreEqual(
dict1.OrderBy(kv => kv.Key).ToList(),
dict2.OrderBy(kv => kv.Key).ToList()
);
I think that AreDictionariesEqual() just needs another method for List comparison
So if order of entries doesn't matter you can try this:
static bool ListEquals(List<int> L1, List<int> L2)
{
if (L1.Count != L2.Count)
return false;
return L1.Except(L2).Count() == 0;
}
/*
if it is ok to change List content you may try
L1.Sort();
L2.Sort();
return L1.SequenceEqual(L2);
*/
static bool DictEquals(Dictionary<string, List<int>> D1, Dictionary<string, List<int>> D2)
{
if (D1.Count != D2.Count)
return false;
return D1.Keys.All(k => D2.ContainsKey(k) && ListEquals(D1[k],D2[k]));
}
And if order of entries matters, try this:
static bool DictEqualsOrderM(Dictionary<string, List<int>> D1, Dictionary<string, List<int>> D2)
{
if (D1.Count != D2.Count)
return false;
//check keys for equality, than lists.
return (D1.Keys.SequenceEqual(D2.Keys) && D1.Keys.All(k => D1[k].SequenceEqual(D2[k])));
}
The accepted answer above will not always return a correct comparison because
using a HashSet to compare 2 lists will not account for duplicate values in the lists.
For instance if the OP had:
var dict1 = new Dictionary<string, List<int>>() { { "A", new List<int>() { 1, 2, 1 } } };
var dict2 = new Dictionary<string, List<int>>() { { "A", new List<int>() { 2, 2, 1 } } };
Then the result of the dictionary comparison is they are equal, when they are not. The only solution I see is to sort the 2 list and compare the values by index, but I'm sure someone smarter then me can come up with a more efficient way.
Here is a way using Linq, probably sacrificing some efficiency for tidy code. The other Linq example from jfren484 actually fails the DoesOrderValuesMatter() test, because it depends on the default Equals() for List<int>, which is order-dependent.
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
string dict1string = String.Join(",", dict1.OrderBy(kv => kv.Key).Select(kv => kv.Key + ":" + String.Join("|", kv.Value.OrderBy(v => v))));
string dict2string = String.Join(",", dict2.OrderBy(kv => kv.Key).Select(kv => kv.Key + ":" + String.Join("|", kv.Value.OrderBy(v => v))));
return dict1string.Equals(dict2string);
}
If two dictionaries are known to use equivalent implementations of IEqualityComparer, and one wishes to regard as equivalent all keys which that implementation regardss as equivalent, they contain the same number of items, and one (arbitrarily chosen) maps all of the elements keys found in the other to corresponding values from the other, they will be equivalent unless or until one of them is modified. Testing for those conditions will be faster than any approach which does not not assume that both dictionaries use the same IEqualityComparer.
If two dictionaries do not use the same implementation of IEqualityComparer, they should generally not be considered equivalent regardless of the items they contain. For example, a Dictionary<String,String> with a case-sensitive comparer and one with a case-insensitive comparer, both of which contain the key-value pair ("Fred", "Quimby") are not equivalent, since the latter would map "FRED" to "Quimby", but the former would not.
Only if the dictionaries use the same implementation of IEqualityComparer, but if one is interested in a finer-grained definition of key-equality than the one used by the dictionaries and a copy of the key is not stored with each value, it will it be necessary to build a new dictionary for the purpose of testing the original dictionaries for equality. It may be best to delay this step until the earlier test has suggested that the dictionaries seem to match. Then build a Dictionary<TKey,TKey> which maps each key from one of the dictionaries to itself, and then look up all of the other dictionary's keys in that to make sure that they map to things which match. If both dictionaries used case-insensitive comparers, and one contained ("Fred", "Quimby") and the other ("FRED", "Quimby"), the new temporary dictionary would map "FRED" to "Fred", and comparing those two strings would reveal that the dictionaries don't match.
Most of the answers are iterating the dictionaries multiple times while it should be simple:
static bool AreEqual(IDictionary<string, string> thisItems, IDictionary<string, string> otherItems)
{
if (thisItems.Count != otherItems.Count)
{
return false;
}
var thisKeys = thisItems.Keys;
foreach (var key in thisKeys)
{
if (!(otherItems.TryGetValue(key, out var value) &&
string.Equals(thisItems[key], value, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
return true;
}
I like this approach because it gives more details when the test fails
public void AssertSameDictionary<TKey,TValue>(Dictionary<TKey,TValue> expected,Dictionary<TKey,TValue> actual)
{
string d1 = "expected";
string d2 = "actual";
Dictionary<TKey,TValue>.KeyCollection keys1= expected.Keys;
Dictionary<TKey,TValue>.KeyCollection keys2= actual.Keys;
if (actual.Keys.Count > expected.Keys.Count)
{
string tmp = d1;
d1 = d2;
d2 = tmp;
Dictionary<TKey, TValue>.KeyCollection tmpkeys = keys1;
keys1 = keys2;
keys2 = tmpkeys;
}
foreach(TKey key in keys1)
{
Assert.IsTrue(keys2.Contains(key), $"key '{key}' of {d1} dict was not found in {d2}");
}
foreach (TKey key in expected.Keys)
{
//already ensured they both have the same keys
Assert.AreEqual(expected[key], actual[key], $"for key '{key}'");
}
}
public static IDictionary<string, object> ToDictionary(this object source)
{
var fields = source.GetType().GetFields(
BindingFlags.GetField |
BindingFlags.Public |
BindingFlags.Instance).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source) ?? string.Empty
);
var properties = source.GetType().GetProperties(
BindingFlags.GetField |
BindingFlags.GetProperty |
BindingFlags.Public |
BindingFlags.Instance).ToDictionary
(
propInfo => propInfo.Name,
propInfo => propInfo.GetValue(source, null) ?? string.Empty
);
return fields.Concat(properties).ToDictionary(key => key.Key, value => value.Value); ;
}
public static bool EqualsByValue(this object source, object destination)
{
var firstDic = source.ToFlattenDictionary();
var secondDic = destination.ToFlattenDictionary();
if (firstDic.Count != secondDic.Count)
return false;
if (firstDic.Keys.Except(secondDic.Keys).Any())
return false;
if (secondDic.Keys.Except(firstDic.Keys).Any())
return false;
return firstDic.All(pair =>
pair.Value.ToString().Equals(secondDic[pair.Key].ToString())
);
}
public static bool IsAnonymousType(this object instance)
{
if (instance == null)
return false;
return instance.GetType().Namespace == null;
}
public static IDictionary<string, object> ToFlattenDictionary(this object source, string parentPropertyKey = null, IDictionary<string, object> parentPropertyValue = null)
{
var propsDic = parentPropertyValue ?? new Dictionary<string, object>();
foreach (var item in source.ToDictionary())
{
var key = string.IsNullOrEmpty(parentPropertyKey) ? item.Key : $"{parentPropertyKey}.{item.Key}";
if (item.Value.IsAnonymousType())
return item.Value.ToFlattenDictionary(key, propsDic);
else
propsDic.Add(key, item.Value);
}
return propsDic;
}
Comparing dictionary using string keys is way more complex than what it looks at first glance.
Dictionary<TKey,TValue> uses an IEqualityComparer<TKey> every time you access an entry in the dictionary to compare your input with the actual entries. The comparer is also used for hash calculations, which serves as some kind of index for faster random access to the entries. Trying to compare dictionaries with different comparers may have some side effects on key sorting and equality considerations for the key-value pair. The key point here is you need to compare the comparers too when comparing dictionaries.
Dictionary<TKey,TValue> also provides collections of keys and values, but they are unsorted. The keys and values collections are consistent inside the dictionary (the nth key is the nth value's key), but not across instances. This means we'll have to work with KeyValuePairs<TKey,TValue> and sort them by key on both dictionaries before comparing them.
However, the comparers in the dictionary only check for equality, it's not able to sort the keys. In order to sort pairs, we'll need a new IComparer<TKey> instance, which is another interface than IEqualityComparer<TKey>. But there's a trap here: default implementations of these two interfaces are not consistent. When you create a dictionary using the default contructor, the class will instanciate a GenericEqualityComparer<TKey> if TKey implements IEquatable<TKey>, which require TKey to implement bool Equals(TKey other); (otherwise, it will fallback to an ObjectEqualityComparer). If you create default Comparer, that will instanciate a GenericComparer<TKey> if TKey implements IComparable<TKey>, which will require TKey to implement int CompareTo(TKey other); (otherwise it will default to an ObjectComparer). Not all types implement both interfaces, and those who do sometimes use different implementations. There is a risk that two different keys (according to Equals) are sorted identically (according to CompareTo). In that case, there's a risk on the key sorting consitency.
Fortunately, string implements both interfaces. Unfortunately, its implementations are NOT consistent: CompareTo depends on the current culture to sort items, whereas Equals does not ! The solution to this problem is to inject a custom comparer to the dictionary, which provides consistent implementation of both interface. We can use StringComparer for that rather than relying on the default implementation. Then we'll simply get the dictionary comparer, cast it, and use it for sorting keys. Also, StringComparer allows comparing the comparers, so we can ensure both dictionaries use the same one.
First, we need a way to compare the values of the dictionary. Since you want to compare lists of int without order, we'll implement an generic equality comparer that sorts items and SequenceEqual them.
internal class OrderInsensitiveListComparer<TValue>
: IEqualityComparer<IEnumerable<TValue>>
{
private readonly IComparer<TValue> comparer;
public OrderInsensitiveListComparer(IComparer<TValue> comparer = null)
{
this.comparer = comparer ?? Comparer<TValue>.Default;
}
public bool Equals([AllowNull] IEnumerable<TValue> x, [AllowNull] IEnumerable<TValue> y)
{
return x != null
&& y != null
&& Enumerable.SequenceEqual(
x.OrderBy(value => value, comparer),
y.OrderBy(value => value, comparer));
}
public int GetHashCode([DisallowNull] IEnumerable<TValue> obj)
{
return obj.Aggregate(17, (hash, item) => hash * 23 ^ item.GetHashCode());
}
}
Now, we've got the values covered, but we also need to compare KeyValuePair. It is a simple ref struct, so we don't need to check for nulls. We'll simply delegate the comparison to two comparers : one for the key, another for the value.
internal class KeyValuePairComparer<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>
{
private readonly IEqualityComparer<TKey> key;
private readonly IEqualityComparer<TValue> value;
public KeyValuePairComparer(
IEqualityComparer<TKey> key = null,
IEqualityComparer<TValue> value = null)
{
this.key = key ?? EqualityComparer<TKey>.Default;
this.value = value ?? EqualityComparer<TValue>.Default;
}
public bool Equals([AllowNull] KeyValuePair<TKey, TValue> x, [AllowNull] KeyValuePair<TKey, TValue> y)
{
// KeyValuePair is a struct, you can't null check
return key.Equals(x.Key, y.Key) && value.Equals(x.Value, y.Value);
}
public int GetHashCode([DisallowNull] KeyValuePair<TKey, TValue> obj)
{
return 17 * 23 ^ obj.Key.GetHashCode() * 23 ^ obj.Value.GetHashCode();
}
}
Now, we can implement the dictionary comparer. We do null check and compare the dictionaries comparers. Then we consider the dictionary as a simple enumerable of KeyValuePair and SequenceEqual them after sorting them by key. For that, we cast the dictionary comparer and delegate the comparison to the KeyValueComparer.
internal class DictionaryComparer<TValue> : IEqualityComparer<Dictionary<string, TValue>>
{
private readonly IEqualityComparer<TValue> comparer;
public DictionaryComparer(
IEqualityComparer<TValue> comparer = null)
{
this.comparer = comparer ?? EqualityComparer<TValue>.Default;
}
public bool Equals([AllowNull] Dictionary<string, TValue> x, [AllowNull] Dictionary<string, TValue> y)
{
return x != null
&& y != null
&& Equals(x.Comparer, y.Comparer)
&& x.Comparer is StringComparer sorter
&& Enumerable.SequenceEqual(
x.AsEnumerable().OrderBy(pair => pair.Key, sorter),
y.AsEnumerable().OrderBy(pair => pair.Key, sorter),
new KeyValuePairComparer<string, TValue>(x.Comparer, comparer));
}
public int GetHashCode([DisallowNull] Dictionary<string, TValue> obj)
{
return new OrderInsensitiveListComparer<KeyValuePair<string, TValue>>()
.GetHashCode(obj.AsEnumerable()) * 23 ^ obj.Comparer.GetHashCode();
}
}
Finally, we only need to instanciate comparers and let them do the work.
private bool AreDictionariesEqual(Dictionary<string, List<int>> dict1, Dictionary<string, List<int>> dict2)
{
return new DictionaryComparer<List<int>>(
new OrderInsensitiveListComparer<int>())
.Equals(dict1, dict2);
}
However, for this to work, we need to use a StringComparer in every dictionary.
[TestMethod]
public void DoesOrderValuesMatter()
{
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>(StringComparer.CurrentCulture);
// more stuff
}
The function shown below can be accommodated to perform any generic comparison:
public bool AreDictionaryEquals(Dictionary<ulong?, string> dictionaryList1, Dictionary<ulong?, string> dictionaryList2)
{
if (dictionaryList1.Count != dictionaryList2.Count)
return false;
IDictionary<ulong?, string> orderedList1 = new Dictionary<ulong?, string>();
IDictionary<ulong?, string> orderedList2 = new Dictionary<ulong?, string>();
foreach (var itemDict1 in dictionaryList1.OrderByDescending(key => key.Id))
{
orderedList1.Add(itemDict1.Id, itemDict1.PropertyX);
}
foreach (var itemDict2 in dictionaryList2.OrderByDescending(key => key.Id))
{
orderedList2.Add(itemDict2.Id, itemDict2.PropertyX);
}
//check keys and values for equality
return (orderedList1.Keys.SequenceEqual(orderedList2.Keys) && orderedList1.Keys.All(k => orderedList1[k].SequenceEqual(orderedList2[k])));
}
1- If the length of both dictionaries is not equal we can safely return false.
2- Then, we proceed to sort both dictionaries using the value of the keys. The reason for doing this is you could have situations like this:
Dictionary A: [1,A], [3,B]
Dictionary B: [3,B], [1,A]
Even though the order is not the same, the content of both can be considered equal.
Finally:
3- We compare both sorted sequences and retrieve the result of this comparison.
I have a LINQ Distinct() statement that uses my own custom comparer, like this:
class MyComparer<T> : IEqualityComparer<T> where T : MyType
{
public bool Equals(T x, T y)
{
return x.Id.Equals(y.Id);
}
public int GetHashCode(T obj)
{
return obj.Id.GetHashCode();
}
}
...
var distincts = bundle.GetAllThings.Distinct(new MyComparer<MySubType>());
This is all fine and dandy and works as I want. Out of curiosity, do I need to define my own Comparer, or can I replace it with a delegate? I thought I should be able to do something like this:
var distincts = bundle.GetAllThings.Distinct((a,b) => a.Id == b.Id);
But this doesn't compile. Is there a neat trick?
Distinct takes an IEqualityComparer as the second argument, so you will need an IEqualityComparer. It's not too hard to make a generic one that will take a delegate, though. Of course, this has probably already been implemented in some places, such as MoreLINQ suggested in one of the other answers.
You could implement it something like this:
public static class Compare
{
public static IEnumerable<T> DistinctBy<T, TIdentity>(this IEnumerable<T> source, Func<T, TIdentity> identitySelector)
{
return source.Distinct(Compare.By(identitySelector));
}
public static IEqualityComparer<TSource> By<TSource, TIdentity>(Func<TSource, TIdentity> identitySelector)
{
return new DelegateComparer<TSource, TIdentity>(identitySelector);
}
private class DelegateComparer<T, TIdentity> : IEqualityComparer<T>
{
private readonly Func<T, TIdentity> identitySelector;
public DelegateComparer(Func<T, TIdentity> identitySelector)
{
this.identitySelector = identitySelector;
}
public bool Equals(T x, T y)
{
return Equals(identitySelector(x), identitySelector(y));
}
public int GetHashCode(T obj)
{
return identitySelector(obj).GetHashCode();
}
}
}
Which gives you the syntax:
source.DistinctBy(a => a.Id);
Or, if you feel it's clearer this way:
source.Distinct(Compare.By(a => a.Id));
It's unfortunate that Distinct doesn't come up with such an overload, so what you have is a good option.
With MoreLinq, you can use the DistinctBy operator.
var distincts = bundle.GetAllThings.DistinctBy(a => a.Id);
You might also want to consider writing a generic ProjectionEqualityComparer that can turn the appropriate delegate into an IEqualityComparer<T> implementation, such as the one listed here.
Here is my perverse dirty little vanilla C# trick:
entities
.GroupBy(e => e.Id)
.Select(g => g.First())
This link shows how to create the extension method to be able to use Distinct in the manner you gave. You'll need to write two Distinct extension methods, and one IEqualityComparer.
Here's the code, from the site:
public static class Extensions
{
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer)
{
return source.Distinct(new DelegateComparer<T>(comparer));
}
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, Func<T, T, bool> comparer, Func<T,int> hashMethod)
{
return source.Distinct(new DelegateComparer<T>(comparer,hashMethod));
}
}
public class DelegateComparer<T> : IEqualityComparer<T>
{
private Func<T, T, bool> _equals;
private Func<T,int> _getHashCode;
public DelegateComparer(Func<T, T, bool> equals)
{
this._equals = equals;
}
public DelegateComparer(Func<T, T, bool> equals, Func<T,int> getHashCode)
{
this._equals = equals;
this._getHashCode = getHashCode;
}
public bool Equals(T a, T b)
{
return _equals(a, b);
}
public int GetHashCode(T a)
{
if (_getHashCode != null)
return _getHashCode(a);
else
return a.GetHashCode();
}
}
As of NET6, a DistinctBy extension method was added to the library in System.Linq.
example:
Planet[] planets =
{
Planet.Mercury,
Planet.Venus,
Planet.Earth,
Planet.Mars,
Planet.Jupiter,
Planet.Saturn,
Planet.Uranus,
Planet.Neptune,
Planet.Pluto
};
foreach (Planet planet in planets.DistinctBy(p => p.Type))
{
Console.WriteLine(planet);
}
// This code produces the following output:
// Planet { Name = Mercury, Type = Rock, OrderFromSun = 1 }
// Planet { Name = Jupiter, Type = Gas, OrderFromSun = 5 }
// Planet { Name = Uranus, Type = Liquid, OrderFromSun = 7 }
// Planet { Name = Pluto, Type = Ice, OrderFromSun = 9 }
My Code looks like this :
Collection<NameValueCollection> optionInfoCollection = ....
List<NameValueCollection> optionInfoList = new List<NameValueCollection>();
optionInfoList = optionInfoCollection.ToList();
if(_isAlphabeticalSoting)
Sort optionInfoList
I tried optionInfoList.Sort() but it is not working.
Using the sort method and lambda expressions, it is really easy.
myList.Sort((a, b) => String.Compare(a.Name, b.Name))
The above example shows how to sort by the Name property of your object type, assuming Name is of type string.
If you just want Sort() to work, then you'll need to implement IComparable or IComparable<T> in the class.
If you don't mind creating a new list, you can use the OrderBy/ToList LINQ extension methods. If you want to sort the existing list with simpler syntax, you can add a few extension methods, enabling:
list.Sort(item => item.Name);
For example:
public static void Sort<TSource, TValue>(
this List<TSource> source,
Func<TSource, TValue> selector)
{
var comparer = Comparer<TValue>.Default;
source.Sort((x, y) => comparer.Compare(selector(x), selector(y)));
}
public static void SortDescending<TSource, TValue>(
this List<TSource> source,
Func<TSource, TValue> selector)
{
var comparer = Comparer<TValue>.Default;
source.Sort((x, y) => comparer.Compare(selector(y), selector(x)));
}
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
}
List<Person> people = new List<Person>();
people.Sort(
delegate(Person x, Person y) {
if (x == null) {
if (y == null) { return 0; }
return -1;
}
if (y == null) { return 0; }
return x.FirstName.CompareTo(y.FirstName);
}
);
You need to set up a comparer that tells Sort() how to arrange the items.
Check out List.Sort Method (IComparer) for an example of how to do this...