269. ToStringComparer

קרה לי לא פעם שהייתי צריך להשוות שני MethodInfoים, או לחלופין שני Typeים.

הדבר נשמע פשוט, ונראה שאפשר להשתמש פשוט בEquals.

אלא שיש כאן טריק. תסתכלו על הקוד הבא:

1
2
3
4
5
6
7
public class GenericClass<T>
{
public void DoSomethingGeneric(string notAGenericString)
{
// ...
}
}

עכשיו נניח שיש לנו את המחלקה הבאה:

1
2
3
4
5
6
7
8
9
10
11
12
public class GenericProvider
{
public GenericClass<T> ProvideSomethingGeneric<T>()
{
return null;
}
public GenericClass<T> ProvideAnotherGenericThing<T>()
{
return null;
}
}

עכשיו נניח שאנחנו קוראים לקוד הבא:

1
2
3
4
5
6
7
8
9
10
Type genericProviderType = typeof (GenericProvider);
MethodInfo provideSomethingGeneric =
genericProviderType.GetMethod("ProvideSomethingGeneric");
MethodInfo provideAnotherGenericThing =
genericProviderType.GetMethod("ProvideAnotherGenericThing");
Type firstGenericType = provideSomethingGeneric.ReturnType;
Type secondGenericType = provideAnotherGenericThing.ReturnType;

למרבה ההפתעה אם נקרא לקוד הבא לא נכנס לתנאי:

1
2
3
4
if (firstGenericType.Equals(secondGenericType))
{
// False
}

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

הסיבה היא שהארכיטקטים של מיקרוסופט החליטו ששני הטיפוסים האלה המייצגים את GenericClass, הם לא שווים.

למה? הסיבה היא כי הם נוצרים ע"י פרמטרים גנריים שונים: הראשון מתקבל ע"י שליחת פרמטר גנרי לפונקציה ProvideSomethingGeneric, ואילו השני מתקבל ע"י שליחת פרמטר גנרי לפונקציה ProvideAnotherGenericThing. אבל אלה שני פרמטרים גנריים שונים!


בפרט, אם ננסה להשוואות מתודות של הטיפוסים, גם לא נקבל שוויון:

1
2
3
4
5
6
7
8
9
10
MethodInfo firstGenericTypeMethod =
firstGenericType.GetMethod("DoSomethingGeneric");
MethodInfo secondGenericTypeMethod =
secondGenericType.GetMethod("DoSomethingGeneric");
if (firstGenericTypeMethod.Equals(secondGenericTypeMethod))
{
// False
}

הדבר הזה יכול להפריע לנו להשוות מתודות.

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


איך אפשר לפתור את זה?

בעצם מעניין אותנו אם לשתי המתודות יש אותה החתימה.

הברקה מסוימת (שהיא גם Patch) היא שהToString של MethodInfo מחזיר משהו… שדומה לחתימה של המתודה!

לכן אפשר לנצל עובדה זו. ניצור IEqualityComparer שמשווה אובייקטים לפי הToStringים שלהם:

1
2
3
4
5
6
7
8
9
10
11
12
public class ToStringComparer<T> : IEqualityComparer<T>
{
public bool Equals(T x, T y)
{
return x.ToString() == y.ToString();
}
public int GetHashCode(T obj)
{
returnobj.ToString().GetHashCode();
}
}

כעת נוכל להשוואות מתודות וטיפוסים כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
IEqualityComparer<MethodInfo> methodComparer = new ToStringComparer<MethodInfo>();
if (methodComparer.Equals(firstGenericTypeMethod, secondGenericTypeMethod))
{
// True
}
IEqualityComparer<Type> typeComparer = new ToStringComparer<Type>();
if (typeComparer.Equals(firstGenericType, secondGenericType))
{
// True
}

הדבר יכול מאוד לעזור לנו בכל המקומות בFramework המקבלים IEqualityComparer. (ראו גם טיפים מספר 111-116)

צום קל וגמר חתימה טובה!

שתף