273. Immutable objects and dictionary keys

רובנו מכירים את הטיפוס של Dictionary המאפשר לנו לבצע מיפוי בין Instanceים מסוג אחד לInstanceים מסוג אחר.

טיפוס זה מאוד חזק ומקל עלינו את האפשרות לבצע תהליכים תכנותיים סבוכים.

אחת הטעויות הנפוצות היא לתת לDictionary בתור הKey אובייקט שהוא Mutable. (כלומר, אובייקט שהוא בר שינוי)

למה?

יש לזה מספר סיבות: הסיבה העיקרית היא שDictionary ממפהInstance בודד לInstance בודד אחר.

עכשיו, נניח שמיפינו שני אובייקטים שונים, כל אחד לInstance אחר בDictionary:

זה האובייקט שלנו (הEquals באדיבות הReShaper)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class MyMutableObject
{
public string Name
{
get;
set;
}
public bool Equals(MyMutableObject other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return Equals(other.Name, Name);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (!(obj is MyMutableObject))
{
return false;
}
return Equals((MyMutableObject) obj);
}
public override int GetHashCode()
{
if (Name != null)
{
return Name.GetHashCode();
}
return 0;
}
}

ויצרנו שני מיפויים:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Dictionary<MyMutableObject, int> mutableToNumber =
new Dictionary<MyMutableObject, int>();
MyMutableObject mutable =
new MyMutableObject()
{
Name = "Jason"
};
MyMutableObject mutable2 =
new MyMutableObject()
{
Name = "Jefferson"
};
mutableToNumber[mutable] = 3;
mutableToNumber[mutable2] = 4;

עכשיו שינינו את אחד הmutable:

1
mutable2.Name = "Jason";

עכשיו מה שקורה בעצם זה שיש לנו בDictionary את אותו Key ביחס לEquals פעמיים. במצב זה לא ברור איזה ערך אנחנו מצפים שיחזור.

סיבה נוספת היא המימוש הפנימי של Dictionary: Dictionary משתמש בGetHashCode וEquals כדי לאתר איברים בו.

במידה והGetHashCode שלנו הוא לא קונסיסטנטי עם הEquals, ייתכן שהDictionary שלנו לא יצליח למצוא את הKey, למרות שהוא נמצא בו. הדבר הזה יכול לקרות אם הGetHashCode מתבסס על דברים שהם לא Immutable!

לכן כשאתם כותבים מחלקה שאתם מעוניינים שתהיה Key של Dictionary, תדאגו שהיא תהיה Immutable.

אם אתם לא רוצים לממש לה GetHashCode וEquals זה גם בסדר, אתם יכולים לספק IEqualityComparer לDictionary שמשווה את האובייקטים בצורה נכונה.

חג שמח, בלי שינויים לא שגורים!

שתף