הקוד הוא קצת ארוך, אבל גם מעניין. שימו לב שהשוואה כאן היא טיפה יותר strict – משווים את המתודות השמות, ערכי ההחזר, סוגי הפרמטרים והפרמטרים הגנריים. זה לא מושלם, למשל השוואה של שתי מתודות גנריות שונות המקבלות IEnumerable<T> לא תחזיר אמת, אבל זה נראה כמו בסיס די טוב. (הייתי כותב EqualityComparer של Type שמשווה באופן דומה Typeים, ומשתמש בו במקום הפונקציה EqualSignatureTypes)
Type firstGenericType = provideSomethingGeneric.ReturnType;
Type secondGenericType = provideAnotherGenericThing.ReturnType;
למרבה ההפתעה אם נקרא לקוד הבא לא נכנס לתנאי:
1
2
3
4
if (firstGenericType.Equals(secondGenericType))
{
// False
}
לפני שאתם מחליטים לפרוש מC# ומעולם התוכנה, ננסה להסביר למה זה קורה.
הסיבה היא שהארכיטקטים של מיקרוסופט החליטו ששני הטיפוסים האלה המייצגים את GenericClass, הם לא שווים.
למה? הסיבה היא כי הם נוצרים ע"י פרמטרים גנריים שונים: הראשון מתקבל ע"י שליחת פרמטר גנרי לפונקציה ProvideSomethingGeneric, ואילו השני מתקבל ע"י שליחת פרמטר גנרי לפונקציה ProvideAnotherGenericThing. אבל אלה שני פרמטרים גנריים שונים!
בפרט, אם ננסה להשוואות מתודות של הטיפוסים, גם לא נקבל שוויון:
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>
{
publicboolEquals(T x, T y)
{
return x.ToString() == y.ToString();
}
publicintGetHashCode(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)
בהמשך לפעמים הקודמות, קיים Out of the box בFramework הProperty הסטטי בשם StructuralComparisons.StructuralComparer.
בדומה לStructuralComparisons.StructuralEqualityComparer, הProperty הזה מאפשר לנו להשוואות שני IStructuralComparable ע”י קריאה לCompareTo שלהם והעברת עצמו:
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
internalclassStructuralComparer : IComparer
{
publicintCompare(object x,object y)
{
if (x == null)
{
if (y != null)
{
return-1;
}
else
{
return0;
}
}
else
{
if (y == null)
{
return1;
}
IStructuralComparable structuralComparable = x as IStructuralComparable;
if (structuralComparable != null)
{
return structuralComparable.CompareTo(y, this);
}
else
{
return Comparer.Default.Compare(x, y);
}
}
}
}
שוב, הדבר טוב אם אנחנו מרוצים מההשוואה הרגילה של הטיפוסים שלנו (המתבצעת באמצעות השורה Comparer.Default.Compare(x, y);, ע"י קריאה לCompareTo של IComparable, במידה והטיפוסים שלנו מממשים אותו).
במידה ואנחנו לא מרוצים מההשוואה הרגילה של הטיפוסים שלנו, נצטרך לכתוב Comparer מיוחד היודע לקבל Comparer איתו הוא משווה את האיברים (במקום קריאה לComparer.Default.Compare(x, y);, נקרא לComparer אותו קיבל הComparer שלנו)
בדומה לפעמים הקודמות, קיימים לנו בFramework הממשקים IComparable וIComparer המאפשרים לנו להשוואות Instanceים של אובייקטים.
(ראו גם טיפים מספר 117-125)
גם כאן, לפעמים היינו רוצים להשוואות מבנים מורכבים מסוימים בעזרת איזשהו Comparer:
לדוגמה, נניח שיש לנו מערך של מערכים, יכול להיות שאנחנו מעוניינים להשוות את המערכים הפנימיים לפי המקסימום שלהם.
דוגמה נוספת, נניח שיש לנו רשימה של אנשים. יכול להיות שנניח אנחנו מעוניינים להשוות אנשים לפי הגילאים שלהם, אבל היינו רוצים להשוות אותם לפי טווחי גילאים, למשל ילדים/נוער/מבוגרים.
גם כאן, נוסף לנו בFramework 4.0 ממשק בשםIStructuralComparable המאפשר לנו להשוות אובייקט מורכב לפי האובייקטים הפנימיים שלו בעזרתComparer.
הממשק נראה כך:
1
2
3
4
publicinterfaceIStructuralComparable
{
intCompareTo(object other, IComparer comparer);
}
גם פה, מערכים וTupleים מממשים אותו Explicitly (ראו גם טיפ מספר 82):
לדוגמה, נניח שיש לנו מערך של מערכים ואנחנו מעוניינים להשוות את המערכים הפנימיים לפי המקסימום שלהם:
נכתוב Comparer רגיל:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ArrayMaxComparer : Comparer<int[]>
{
publicoverrideintCompare(int[] x, int[] y)
{
int firstMax = x.Max();
int secondMax = y.Max();
if (firstMax > secondMax)
{
return1;
}
elseif (firstMax < secondMax)
{
return-1;
}
return0;
}
}
ואז נוכל להשתמש בו בשביל להשוות שני מערכים באותו גודל של מערכים:
numbersStructuralComparable.CompareTo(otherNumbers, new ArrayMaxComparer()); // -1
קצת לא ברור איך הדבר הזה ממומש עבור מערכים, ולכן דורש הסבר:
מתבצעת השוואה לקסיקוגרפית על המערכים לפי הComparer שמועבר. מה זאת אומרת?
תחשבו על זה כמו השוואה במילון – רצים על כל האיברים של המערך הראשון והשני במקביל. משווים כל שני איברים עד שמוצאים שני איברים שהם לא שווים מבחינת הComparer. מחזירים את התוצאה של הCompareTo של שני האיברים הנ"ל (האיבר המתאים מהמערך הראשון והאיבר המתאים מהמערך השני)
גם עבור Tuple הדבר מומש בצורה טובה – משווים רכיב רכיב עד שמוצאים רכיב עבורו לא מתקיים שהCompareTo בין הרכיבים של שני הInstanceים הוא 0. ברגע שמוצאים כזה, מחזירים את התוצאה.
זה מתאים למקרים בהם אנחנו מעוניינים להשוות את הטיפוסים שלנו לפי שדות, כאשר יש עדיפות לשדות, למשל קודם לפי שם פרטי, לאחר מכן לפי שם משפחה, אחר כך לפי גיל וכו’…
if (StructuralComparisons.StructuralEqualityComparer.Equals(theNumbers, evil))
{
// True
}
יש פה משהו קצת מוזר: איפה הEqualityComparer שדיברנו עליו פעם קודמת?
למרבה הצער, אנחנו לא יכולים לציין עם איזה EqualityComparer תתבצע השוואת האיברים, ולכן היא מתבצעת עם… StructuralComparisons.StructuralEqualityComparer עצמו:
הכרנו בעבר את הממשק IEqualityComparerהמאפשר לנו לציין כיצד להשוות שני איברים מטיפוס כלשהו. (ראו גם טיפים מספר 76-80, 111-116)
כפי שבוודאי חלקנו התאכזב לגלות, יש מספר בעיות עם הממשק הזה. למשל, נניח שאנחנו מעוניינים להשוות שני מערכים – היינו רוצים לבדוק כל איבר במקום מסוים במערך הראשון שווה לאיבר באותו המקום במערך השני, לפי איזשהוIEqualityComparer.
בFramework 4.0 החליטו להוסיף ממשק בשם IStructuralEquatable המפשט את הפתרון של בעיות אלה. הממשק הוא ממשק המציין שאפשר להשוות את האובייקט שלנו עם Instance אחר, לפי המבנה הפנימי.
למה הכוונה? אם נזכר למשל בטיפ מספר 77, מתקיים ששוויון Equals של Value Types, מתקיים אם הStructים שווים רכיב רכיב, כלומר שכל השדות של הInstance הראשון שווים לכל השדות של הInstance השני.
כאמור למעלה, היינו רוצים שתהיה לנו אופציה להשוות גם, למשל, מערכים בשיטה זו – רכיב-רכיב. הממשק הזה מציין שהטיפוס שלנו תומך בהשוואת רכיב-רכיב כלשהי.
מנגנון הHashing (גיבוב) הוא מנגנון המאפשר לבצע “הצפנה” חד כיוונית.
הרעיון הוא להמיר מידע בגודל לא מוגבל, לאובייקט כלשהו בעל גודל קבוע המייצג אותו.
ההמרה מתבצעת בצורה שהיא רגישה לרעשים, כך שאם מתבצע שינוי קטן במידע, יתבצע שינוי דרסטי בHash.
הדבר שימושי במספר מקומות: לדוגמה, בהשוואת קבצים, בד”כ בדיקה שהHash שלהם לפי אלגוריתם מסוים הוא שווה, מספיק אמינה כדי להניח שהקבצים זהים.
בנוסף, בשמירת סיסמאות: יש לא מעט Databaseים שלא שומרים את הסיסמאות של המשתמשים שלהם, אלא רק את הHash של הסיסמאות, כך שאם מישהו נכנס לDatabase, הוא לא יוכל לקבל מידע רגיש. כאשר משתמש מנסה להתחבר למערכת, מתבצעת השוואה של הסיסמה שלו לHash ששמור במערכת.
אחד אלגוריתמי הHash הנפוצים בעולם הוא אלגוריתם הMD5.
כיום הוא לא נחשב האלגוריתם הכי טוב, אך הוא עדיין מספיק שימושי.
בFramework יש לנו תמיכה במספר אלגוריתמי גיבוב, ביניהם גם באלגוריתם MD5.
השימוש בו הוא כזה:
1
2
3
4
5
6
MD5 algorithm = MD5.Create();
using (FileStream stream = newFileStream(@"MyFile.txt",FileMode.Open))
{
byte[] hash = algorithm.ComputeHash(stream);
}
מאחר ואלגוריתם MD5 מחזיר לנו מערך של 16 בתים, בד"כ נוח להסתכל על הHash כGuid:
1
2
3
4
5
6
7
8
9
10
11
Guid hashGuid;
MD5 algorithm = MD5.Create();
using (FileStream stream = newFileStream(@"MyFile.txt",FileMode.Open))
privatestaticboolTryParse(stringvalue, out Guid result)
{
if (mGuidRegex.IsMatch(value))
{
result = new Guid(value);
returntrue;
}
else
{
result = Guid.Empty;
returnfalse;
}
}
שימו לב שהRegex הזה סבבה והכל, אבל לא מתאים לכל הFormatים החוקיים של Guidים.
אפשר לנסות לשפר את הRegex, אבל נשים לב שככל שננסה לשפר אותו יותר, כך וואלידאציה עליו תהיה יותר כבדה…
בסופו של דבר, אם אנחנו יודעים שהGuidים שאנחנו מצפים לקבל הם בפורמט ספציפי, נוכל לכתוב Regex מתאים לו ולחסוך את תפיסת הException. במידה ולא נוכל לאפיין את כל הGuidים שאנחנו מצפים לקבל, כנראה הפתרון הכי טוב הוא להשתמש בtry-catch, מאחר והוואלידאציה תעלה לנו לא פחות מתפיסת הException מבחינת ביצועים…