לפעמים אנחנו רוצים שיהיה לנו איזה Collection, שכל מה שמעניין אותנו זה שנוכל לבדוק האם איבר נמצא בו או לא בדרך מהירה.
לדוגמה, נניח שיש לנו הרבה ת.ז. של אנשים שטיפלנו בהם ולא אכפת לנו מספר הפעמים שטיפלנו בהם, אלא רק אם זו הפעם הראשונה שטיפלנו בהם או לא.
אם נשתמש בList, או בCollection סטנדרטי אחר חיפוש איבר יקח $ o(n) $, כיוון שפשוט אנחנו עוברים על כל האיברים ובודקים על כל אחד האם הוא שווה לאיבר שאנחנו מחפשים או לאו.
לכן עולה הרעיון של להשתמש במנגנון של GetHashCode בכדי למצוא איבר במהירות.
הדרך המרושלת לעשות זאת זה לאנוס Dictionary בשביל הצורך הזה:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Dictionary<string, bool> bandMembers =
new Dictionary<string, bool>();
bandMembers["George"] = true;
bandMembers["Paul"] = true;
bandMembers["John"] = true;
bandMembers["Ringo"] = true;
// ...
if (bandMembers.ContainsKey("George"))
{
// ...
}
if (bandMembers.ContainsKey("Gaga"))
{
// ...
}
ContainsKey של Dictionary אכן עובד עם GetHashCode ולכן יספק תשובה באופן מהיר יותר, אך זה שימוש לא נכון בDictionary מאחר ואנחנו בכלל לא מתייחסים לValue.
לפני Framework 3.5 לא היה פתרון כל כך טוב בBCL והאלטרנטיבה הכי טובה הייתה להשתמש בSet של PowerCollections, או לממש בעצמנו מנגנון כזה.
בFramework 3.5 הוסיפו מחלקה בשם HashSet<T>, בnamespace System.Collections.Generic בSystem.Core.dll.
המחלקה בעצם מייצגת קבוצה מתמטית סופית, עליה ניתן לשאול אם איבר נמצא או לא.
היא משתמשת בGetHashCode כדי לענות על שאלה זו בצורה מהירה.
הדוגמה הקודמת תכתב כך:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HashSet<string> bandMembers =
new HashSet<string>();
bandMembers.Add("George");
bandMembers.Add("Paul");
bandMembers.Add("John");
bandMembers.Add("Ringo");
if (bandMembers.Contains("George"))
{
// ...
}
if (bandMembers.Contains("Gaga"))
{
// ...
}
בנוסף, נציין כי לHashSet המון מתודות של מימושים מתורת הקבוצות, למשל חיתוך ואיחוד קבוצות, הפרש סימטרי ועוד.
בזמן קומפילציה מומר הcompile time const לערך. הIL אינה מכירה את הקבוע הנ"ל.
כמו כן ניתן להגדיר קבועים רק לPrimitive Types. למשל קטע הקוד הבא לא יתקמפל :
1
publicconst DateTime myDate = new DateTime(1987 , 1 , 22);
נציין כי חסכון בזמן ריצת התוכנית (זניח בהשוואה לRuntime consts). בנוסף לרב, שימוש בconsts יצור assembly גדול יותר ( כיוון שהIL מחליף ref לפרמטר בערך עצמו)
נשתמש בconst כאשר נרצה לבצע
שימוש בEnums
אתחול ctors של Attributes באמצעות consts
Primitive constant (שאינו משתנה בין גרסאות)
Run-time constants
הקבוע מוגדר במקום בזיכרון הניתן להשמה רק בזמן הctor. ז"א שיהיה מקום בזיכרון שיאחסן את הקבוע ובזמן הJIT הוא יומר לקוד.
1
publicstaticreadonlyint myRuntimeConstValue = 66;
נשתמש בRuntime constants כאשר
נרצה להגדיר קבועים מטיפוס reference types
הקבועים שלנו יכולים להשתנות בין גרסאות ( למשל מיקום קובץ דיפולטי) – ראה דוגמא בסוף
נוכל להשתמש בRuntime constant שאינו סטטי על מנת להגדיר קבועים שונים למחלקות שונות
סיכום
באופן כללי נעדיף תמיד להשתמש בRuntime constants על פני Comiple Time constants. הפגיעה בביצועים היא זניחה לעומת הגמישות שהדבר נותן. ( ראה טבלה )
לדוגמא
הקבועים myCompileTimeConstValue וmyRuntimeConstValue מוגדר בassembly – myCommonConstants.dll , והאפליקציה MyApp.exe משתמשת בהם.
כאשר נחליף את ערך הקבועmyCompileTimeConstValue . נצטרך לקמפל נצטרך לקמפל מחדש את הmyCommonConstants.dll
ואת כל הassemblies שמשתמשים בו(!).
כאשר נשנה את הערך myRuntimeConstValue נצטרך לקמפל מחדש רק את הmyCommonConstants.dll
לפעמים אנחנו לא מודעים לסוג של אובייקט, אז אנחנו עושים Castים כך:
1
2
3
4
5
6
7
8
9
10
11
object obj = GimmeObject();
try
{
int initifiedObj = (int) obj;
// Object is int, continue...
}
catch (InvalidCastException)
{
// Object is not int
}
השיטה הזו לא נכונה, כיוון שזה לא נכון להשתמש בExceptionים לFlow הרציף של התוכנה.
כדי לבדוק אם אובייקט הוא מסוג מסוים נשתמש במילת המפתח is, אם אנחנו רוצים לשמור את הערך של ההמרה ולהשתמש בו אח"כ – נשתמש במילת המפתח as.
is יחזיר ערך חיובי אם האובייקט יורש/הוא מהType שניתן, as יחזיר את האובייקט מומר לType שניתן אם הוא יורש ממנו / הוא מהType שניתן – ואם לא, הוא יחזיר את הערך null.
דוגמה לשימוש בis, בדיקה שאובייקט יורש מIDisposable :
1
2
3
4
5
6
object obj = GimmeObject();
if (obj is IDisposable)
{
Console.WriteLine("Object is disposable")
}
דוגמה לשימוש בas, קריאה לDispose של אוסף אובייקטים שחלקם יורשים וחלקם לא מIDisposable :
1
2
3
4
5
6
7
8
9
10
11
IEnumerable<object> objects = GimmeObjects();
foreach (object obj in objects)
{
IDisposable disposable = obj as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
שימו לב כי כתיבת השורה
1
MyClass myClassObj = obj as MyClass;
שקולה לכתיבת השורות הבאות:
1
2
3
4
5
6
7
8
9
10
MyClass myClassObj;
if (obj is MyClass)
{
myClassObj = (MyClass)obj;
}
else
{
myClassObj = null;
}
ולכן לא ניתן להשתמש באופרטור as בשביל ValueTypes (כגון int, DateTime וכו’)