I am learning about dictionaries, and lost in this problem.
class Program
{
static void Main(string[] args)
{
Dictionary<A, B> dict = new Dictionary<A, B>();
A a = new A { A1 = 1, A2 = 2 };
B b = new B { B1 = 3, B2 = "foo" };
dict.Add(a, b);
A c = new A { A1 = 1, A2 = 2 };
Console.WriteLine($"dict.ContainsKey(a): {dict.ContainsKey(a)}");
Console.WriteLine($"dict.ContainsKey(c): {dict.ContainsKey(c)}");
Console.ReadLine();
}
}
class A
{
public int A1 { get; set; }
public int A2 { get; set; }
}
class B
{
public int B1 { get; set; }
public string B2 { get; set; }
}
output for this code is:
dict.ContainsKey(a): True
dict.ContainsKey(c): False
What I want to achieve is get the value of B2
, based on the parameters A1
and A2
. I was hoping that dict.ContainsKey(c)
returned true
, but it does not for some reason.
I could do something like this, which returns foo
, but that seems to be overkill ?:
(from f in dict.Keys where f.A1==1 && f.A2==2 select dict[f].B2).First()
How can I get the value for B2
, If I know a value for A1
and A2
?
CodePudding user response:
When comparing custom classes, you should explain .net how to do it with a help of Equals
and GetHashCode
methods.
If you don't do it, .net will compare references, not values. In your case it can be
class A {
public int A1 { get; set; }
public int A2 { get; set; }
public override bool Equals(object obj) =>
(obj is A other) && (A1 == other.A1) && (A2 == other.A2);
public override int GetHashCode() => unchecked((A1 << 16) ^ A2);
// For debugging
public override string ToString() => $"A1 = {A1}; A2 = {A2}";
}
Now
Dictionary<A, B> dict = new Dictionary<A, B>() {
{new A { A1 = 1, A2 = 2 }, new B { B1 = 3, B2 = "foo" }},
};
A c = new A { A1 = 1, A2 = 2 };
if (dict.ContainsKey(c))
Console.WriteLine($"Contains {c}");
else
Console.WriteLine($"Doesn't contains {c}");
CodePudding user response:
You need to define a class, which implements the IEqualityComparer
to define how to compare keys. You want to compare them based on their properties rather than their references.
class AValueComparer : IEqualityComparer<A>
{
public bool Equals(A x, A y) => x.A1 == y.A1 && x.A2 == y.A2;
public int GetHashCode([DisallowNull] A obj) => HashCode.Combine(obj.A1, obj.A2);
}
Then all you need is to connect this comparer to your dictionary.
[Fact]
public void Test1()
{
//Arrange
var dict = new Dictionary<A, B>(new AValueComparer());
var a = new A { A1 = 1, A2 = 2 };
var b = new B { B1 = 3, B2 = "foo" };
dict.Add(a, b);
var c = new A { A1 = 1, A2 = 2 };
//Act
var doesAExist = dict.ContainsKey(a);
var doesCExist = dict.ContainsKey(c);
//Assert
Assert.Equal(doesAExist, doesCExist);
}
CodePudding user response:
It's false because classes in C# are reference types, and while a
and c
have the same values (i.e A1
and A2
are the same) they are not the same instance, which is what's checked by default. If you want to you can implement a IEqualityComparer which equates A with its values and pass a new instance of that to the Dictionary constructor