200. ItemEventArgs{T} class

לאירועים שאנחנו מקפיצים אנחנו צריכים להעביר EventArgs.

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

מה שיוצא זה שאנחנו יוצרים מלא מחלקות לכל אירוע שאנחנו רוצים להקפיץ.

בניגוד לEventHander, אין מחלקה דומה לEventArgs, אבל זה לא אומר שאנחנו לא יכולים ליצור כזאת בעצמנו:

1
2
3
4
5
6
7
8
public class ItemEventArgs<T> : EventArgs
{
public T Value
{
get;
set;
}
}

אפשר לעשות גם שהProperty הזה יהיה readonly (ראו גם טיפ מספר 14):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ItemEventArgs<T> : EventArgs
{
private readonly T mValue;
public ItemEventArgs(T value)
{
mValue = value;
}
public T Value
{
get { return mValue; }
}
}

ככה נוכל ליצור אירועים משלנו בצורה יותר נחמדה:

1
public event EventHandler<ItemEventArgs<Person>> EmployerAdded;

ולהקפיץ אותו (כרגע בלי בדיקה שהוא תקין):

1
EmployerAdded(this, new ItemEventArgs<Person>(person));

אפשר קצת להתבלבל מרוב משתנים גנריים.

לסיום, אפשר לעשות implicit cast כדי שלא יהיה צורך לעשות new מאולץ לEventArgs:

1
2
3
4
public static implicit operator ItemEventArgs<T>(T source)
{
return new ItemEventArgs<T>(source);
}

ואז להקפיץ בצורה טיפה יותר יפה

1
EmployerAdded(this, person);

סופ"ש עם אירועים מלאי פריטים טוב

שתף

199. EventHandler{TEventArgs} delegate

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

בין השאר, אפשר ליצור Delegateים גנריים, וכך לחסוך כתיבת הרבה Delegateים. (ראו גם טיפים 46-47)

Delegate מצוינים לדוגמה זו הם Delegateים המשמשים אותנו לצורך EventHandlerים:

הם Delegateים מהצורה:

1
2
public delegate void MouseEventHandler(object sender, MouseEventArgs e);
public delegate void KeyEventHandler(object sender, KeyEventArgs e);

כלומר לא מחזירים ערך ומקבלים object וטיפוס שיורש מEventArgs.

בFramework אכן ניצלו עובדה זו ויצרו EventHandler גנרי:

1
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs : EventArgs;

בMSDN ממליצים להשתמש בDelegate זה לשימוש בEventים שלנו.

למען האמת, מיקרוסופט באמת התמידה להשתמש בDelegate זה בקוד שפותח אחרי Framework 2.0 ואכן אם נסתכל על Eventים מWCF או WorkFlow הם באמת משתמשים בטיפוס EventHandler<TEventArgs>.

משום מה בטכנולוגיה WPF שיצאה כבר בFramework 3.0 (סמוך לWCF), אין שימוש בDelegate זה עבור אירועים, אלא משתמשים בDelegate מיוחד עבור כל סוג אירוע…

המשך יום חסר שכפול קוד טוב

שתף

198. XName mystery

לפני כמה זמן תכנתתי לי במה שנקרא Linq To XML, זו ספרייה שהגיעה בFramework 3.5 יחד עם Linq המאפשרת לעבוד בצורה יחסית נוחה עם XMLים.

יצרתי לעצמי איזשהו Element:

1
XElement myElement = new XElement(XName.Get("FirstElement"));

ויצרתי לו מספר Attributes ומספר בנים:

1
2
3
4
5
myElement.Add(new XAttribute(XName.Get("FirstAttribute"), 3));
myElement.Add(new XAttribute(XName.Get("SecondAttribute"), 3));
myElement.Add(new XElement(XName.Get("FirstChild")));
myElement.Add(new XElement("AnotherChild"));
myElement.Add(new XElement(XName.Get("YetAnotherChild")));

קימפלתי וראיתי שהכל מתקמפל. אחר כך שמתי לב שקרה פה משהו מוזר – כל הOverloadים של הConstructorים של XElement וXAttribute מקבלים משהו שנקרא XName.

אלא שבאחת הקריאות (הקריאה האחת לפני האחרונה) לא העברתי לConstructor את הטיפוס הזה, אלא מחרוזת רגילה, כי שכחתי לקרוא לXName.Get.

מה שמדהים בכל הסיפור הזה זה שזה מתקמפל.

התחלתי לחקור את הסיפור:

אולי יש Constructor שלא ראיתי? חיפשתי וחיפשתי ולא מצאתי. אז אולי יש Extension method שיכול לקבל string במקום לקבל XName?

לא יכול להיות דבר כזה, כי אין Extension methods לConstructorים..

בקיצור, המשכתי לחפש. אולי אני טועה, אולי התוכנה לא באמת מתקמפלת? אלא שבדקתי והתוכנה כן מתקמפלת.

מה שהיה ממש מוזר בכל הסיפור הזה, זה שכשעשיתי Go to definition לConstructor הזה, הוא הביא אותי לConstructor שמצפה לקבל XName, כלומר לאותו Constructor שביצעתי קריאה אליו בשורות הקודמות.

אחרי כמה זמן של חיפושים ומחשבות הגעתי לתשובה – קיים Implicit cast בין XName לstring!

1
public static implicit operator XName(string expandedName)

אחרי שגיליתי שהוא קיים, אני מאוד שמח שהוא קיים, כיוון שהוא חוסך כתיבה. אלא שהיה נחמד אם היה יותר קל לזהות את זה.

שימו לב שאם אתם כותבים implicit casts משלכם, שזה לא גלוי לעין שיש הסבה כזאת.

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

המשך יום בלי הרבה תעלומות טוב

שתף

197. implicit operator

ראינו פעם קודמת כיצד אפשר ליצור אופרטורים משלנו.

יש כמה אופרטורים מיוחדים שלא כל כך טריוואלי שאפשר לעשות להם Operator overload.

באחד מהם נפגשנו בעבר (טיפ מספר 2).

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

נניח שיש לנו את הקוד הבא:

1
2
int seven = 7;
float number = seven; // 7.0

מה בדיוק קורה כאן? ובכן, אם נזכר בטיפ על ההבדל בין Value types וReference types נשים לב שבValue types כמו int וfloat אין ירושה.

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

מאחר והסבה זו אינה מאבדת מידע, היא נעשית באופן "שקוף", כלומר implicitly. אם ההסבה הייתה מאבדת מידע (למשל מfloat לint), אז היא לא הייתה שקופה, והיינו צריכים לבצע אותה באופן מפורש.

זה מתבצע באמצעות קריאה של הקומפיילר לפונקציה מתאימה המבצעת הסבה זו.

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

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
public class Radians
{
public double Angle
{
get;
private set;
}
public Radians(double angle)
{
this.Angle = angle;
}
}
public class Degrees
{
public double Angle
{
get;
private set;
}
public Degrees(double angle)
{
this.Angle = angle;
}
}

נוכל לכתוב הסבה שהיא implicit ביניהם. נכתוב למשל בתוך המחלקה של הרדיאנים:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Radians
{
// ...
public static implicit operator Radians(Degrees degrees)
{
return new Radians(degrees.Angle * Math.PI / 180.0);
}
public static implicit operator Degrees(Radians degrees)
{
return new Degrees(degrees.Angle * 180.0 / Math.PI);
}
}

ואז נוכל לבצע המרות באופן שקוף:

1
2
3
Degrees thirtyDegrees = new Degrees(30.0);
Radians radians = thirtyDegrees;
Console.WriteLine(radians.Angle); // Pi/6

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

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

1
2
3
4
public static Degrees SumOfTriangleAngles()
{
return new Radians(180);
}

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

מתי בכל זאת כדאי להשתמש?

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

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

עוד דוגמה היא כזאת – נניח שיש לכם אובייקט שמייצג כתובת ונניח שיש לו ייצוג מחרוזתי לכתובת זו (למשל בעזרת הפונקציה ToString). אפשר ליצור הסבה implicit בין המחרוזת המייצגת את האובייקט לאובייקט שלכם. למשל:

1
IPAddress localHost = "127.0.0.1";

בכל אופן, חשוב לדאוג שההסבה לא תאבד מידע, מאחר והיא מתבצעת באופן בלתי גלוי לעין.

שימו לב שלא נוכל ליצור הסבות ממחלקת אב או הסבות למחלקות אב – אלו שמורות ע"י הקומפיילר.

המשך יום מוסב לטובה

שתף

196. Operator overloading

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

למשל, נניח שאנחנו כותבים מחלקה המייצגת מבנה מתמטי עם חיבור או כפל, למשל מטריצות 5x5, מספרים רציונליים או מספרים מרוכבים, אז היינו רוצים שיהיה אפשר לחבר שני איברים מהמבנה הזה עם אופרטור ה+, כמו שאפשר לחבר שני intים.

ניתן לעשות זאת באמצעות משהו שנקרא Operator overload. מה זה? מדובר בעצם פונקציה סטטית שתפעל ברגע שיפעילו את האופרטור שלנו.

למשל, נניח שיש לנו מחלקה המייצגת מטריצה 5x5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Matrix5x5
{
public Matrix5x5 Multiply(Matrix5x5 other)
{
// ...
}
public Matrix5x5 Multiply(double scalar)
{
// ...
}
public Matrix5x5 Add(Matrix5x5 other)
{
// ...
}
public Matrix5x5 Negate()
{
// ...
}
}

נוכל ליצור אופרטורים מתאימים בצורה הבאה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static Matrix5x5 operator +(Matrix5x5 first, Matrix5x5 second)
{
return (first.Add(second));
}
public static Matrix5x5 operator *(Matrix5x5 first, Matrix5x5 second)
{
return (first.Multiply(second));
}
public static Matrix5x5 operator -(Matrix5x5 first, Matrix5x5 second)
{
return (first.Add(second.Negate()));
}
public static Matrix5x5 operator -(Matrix5x5 matrix)
{
return matrix.Negate();
}

בסה"כ מדובר בפונקציות סטטיות שמבצעות את הקריאות הנכונות.

מה שכן, האופרטורים יכולים לעבוד גם אם אחד האיברים הוא לא איבר של המחלקה שלנו:

למשל, הכפלת מטריצה בקבוע:

1
2
3
4
public static Matrix5x5 operator *(double scalar, Matrix5x5 matrix)
{
return matrix.Multiply(scalar);
}

זהו, ככה אפשר להשתמש בפונקציות האלה בצורה יותר נוחה:

1
2
3
4
Matrix5x5 firstMatrix;
Matrix5x5 secondMatrix;
Matrix5x5 result = firstMatrix + secondMatrix;
Matrix5x5 multipliedByScalar = 9 * firstMatrix;

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

יש לזה חסרון שהוא לא מובן ממבט ראשון – אי אפשר ליצור Operator Overload לממשקים, או בכלל להכריח ממשק שיהיה לו אופרטור כזה (למשל, היינו רוצים שלממשק שמייצג מספר כלשהו, יהיה גם אופרטור חיבור).

שבוע עם אופרטור טוב

שתף

195. Events and framework 4.0

פעם שעברה הכרנו את הAccessorים של Event.

בין השאר ראינו את הקוד שהקומפיילר כותב עבור Eventים להם אין Accessorים מפורשים וראינו שהמימוש הוא משהו כזה:

הקוד הזה

1
2
3
4
public class EventRaiser
{
public event EventHandler Raised;
}

מתמקפל למשהו ששקול לזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class EventRaiser
{
private EventHandler mRaised;
public event EventHandler Raised
{
add
{
mRaised += value;
}
remove
{
mRaised -= value;
}
}
}

זה נכון, עד לC# 4.0. מי שיפתח Reflector על קוד שקומפל לFramework 4.0 יראה את הקוד הבא:

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
public class EventRaiser
{
private EventHandler mRaised;
public event EventHandler Raised
{
add
{
EventHandler handler2;
EventHandler raised = mRaised;
do
{
handler2 = raised;
EventHandler handler3 = (EventHandler)Delegate.Combine(handler2, value);
raised = Interlocked.CompareExchange<EventHandler>(ref mRaised, handler3, handler2);
}
while (raised != handler2);
}
remove
{
EventHandler handler2;
EventHandler raised = this.mRaised;
do
{
handler2 = raised;
EventHandler handler3 = (EventHandler)Delegate.Remove(handler2, value);
raised = Interlocked.CompareExchange<EventHandler>(ref this.mRaised, handler3, handler2);
}
while (raised != handler2);
}
}
}

כשראיתי את הקוד הזה לראשונה לא כל כך הבנתי מה קורה כאן.

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

כדי למנוע זאת, הקומפיילר מייצר קוד כזה מאחורי הקלעים. מה שקורה כאן זה שאנחנו מנסים לעשות Delegate.Combine לDelegate שקיבלנו (שמו כזכור value), זה יוצר Delegate חדש שהוא שרשור של שני הDelegateים.

אחרי שעשינו את זה, אנחנו מכניסים את התוצאה ל handler3. אחרי זה אנחנו קוראים לInterlocked.CompareExchange – זו פונקציה שדומה לקוד הבא:

1
2
3
4
5
6
raised = mRaised;
if (mRaised == handler2)
{
mRaised = handler3;
}

כלומר מחליף את mRaised בDelegate המשורשר בתנאי שmRaised עדיין שווה לhandler2 ומכניס לraised את הערך המקורי שלmRaised. ההבדל המשמעותי בין הקוד שרשמתי פה לשימוש בInterlocked.CompareExchange, זה שInterlocked.CompareExchange הוא אטומי. כלומר, 3 השורות שציינתי מתבצעות בפעולת מעבד אחת, ולכן זה Thread Safe.

כעת הדבר חוזר על עצמו כל עוד raised לא שווה לhandler2.

אנחנו נצא מהלולאה כאשר raised יהיה שווה לhandler2, כלומר כאשר mRaised לפני השרשור של הEvent שווה לmRaised אחרי שרשור הEvent. כלומר כאשר לא התערב לנו Thread אחר בערך של mRaised.

זהו, בסה"כ זה די מפחיד, אבל אפשר להבין מה קורה כאן.

כמובן, אנחנו יכולים כמו פעם קודמת לממש את add וremove בדרך המסורתית במידה ואנחנו לא אוהבים את מה שקורה כאן.

סוף שבוע טוב עם הרבה אירועים בטוחים!

שתף

194. add remove event accessors

פעם קודמת הכרנו קצת את המושג Event.

ראינו שזה בעצם Delegate שהקומפיילר מגביל את השימוש שלו מחוץ למחלקה בה הוא נמצא, כך שיהיה אפשר רק להירשם אליו או להסיר רישום ממנו.

אמרתי גם שזה מזכיר קצת את ההכמסה שמבצע Property.

אך אפשר לומר שProperty נותן יותר גמישות, מאחר ואנחנו יכולים לכתוב איזה קוד שאנחנו רוצים בaccessorים שלו (הgetter או הsetter), ואילו Event זו מגביל לשימוש בadd וremove, אבל בלי כתיבת קוד בגישות אלה.

אלא שקיימת אפשרות גם לכתוב לוגיקה בהוספה והסרה של eventים.

זה מתבצע באמצעות הכתיבה הבאה:

במקום כך:

1
2
3
4
public class EventRaiser
{
public event EventHandler Raised;
}

נכתוב כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class EventRaiser
{
private EventHandler mRaised;
public event EventHandler Raised
{
add
{
mRaised += value;
}
remove
{
mRaised -= value;
}
}
}

זו כתיבה שמאוד דומה לכתיבה של Properties. כאשר תתבצע הרשמה או הסרת רישום לאירוע, יקראו בהתאמה הAccessorים של הadd או הremove.

כך שאם אנחנו רוצים לעשות לוגיקה משלנו, אנחנו יכולים לעשות אותה בAccessorים אלה:

לדוגמה,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private int mNumOfSubscriptions;
public event EventHandler Raised
{
add
{
mRaised += value;
mNumOfSubscriptions++;
}
remove
{
mRaised -= value;
// TODO: Check if the handler was already registered
// TODO: and decrease the number of subscriptions.
mNumOfSubscriptions--;
}
}

למעשה, פונקציות אלה נוצרות מאחורי הקלעים גם אם השתמשנו בSyntax הראשון:

1
2
3
4
public class EventRaiser
{
public event EventHandler Raised;
}

מתקמפל מאחורי הקלעים למשהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class EventRaiser
{
private EventHandler mRaised;
public event EventHandler Raised
{
add
{
this.mRaised += value;
}
remove
{
this.mRaised -= value;
}
}
}

בדומה לProperties שמתמקפלים לפונקציות מהצורה get_Property או set_Property, Eventים מתמקפלים לפונקציות מהצורה add_Event וremove_Event.

בשונה מProperties, בEventים אין לנו את החופש לבחור האם לממש רק את אחד הAccessorים (add או remove), אלא אנחנו מוכרחים לממש את שניהם, מאחר ומי שמשתמש במחלקה שלנו מבחוץ רואה שאנחנו חושפים Event ולכן מבחינתו אנחנו חושפים את שתי האפשרויות.

בד"כ לא נרצה לכתוב מימוש משלנו בתוך הAccessorים אלא במקרים מיוחדים, כמו שחשוב לנו שרישום יהיה Thread-safe, או לחלופין כאשר נרצה לוגיקה משלנו (למשל כשאנחנו חושפים Event של מישהו אחר)

המשך יום מלא רישומים והסרות טוב

שתף

193. The difference between events and delegates

רובנו מכירים את המושג ששמו Delegate מעולם הC# - זהו אובייקט המייצג מצביע לפונקציה כלשהי.

הטיפוס הזה מאפשר לנו לעשות דברים מאוד חזקים וראינו דוגמאות בעבר (למשל, טיפים על Anonymous delegates – מספרים 41-45).

אנחנו מכירים גם את המושג ששמו Event, אבל האם אי פעם תהיתם מה ההבדל בין Event לDelegate?

ובכן Event הוא סוג של Delegate, אלא שהקומפיילר מגביל את השימוש בו.

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

בפרט אם יש לנו Member שהוא מטיפוס Delegate כלשהו, אנחנו יכולים להפוך אותו להוסיף ולהסיר מתודות שהוא יקרא להן ע”י האופרטורים += ו-=.

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

אלא שמבחינת העולם החיצון, כלומר טיפוסים שאינם המחלקה שלנו (אפילו אם הם יורשים ממנה), אנחנו יכולים לבצע בדיוק שתי פעולות: הרשמה לEvent והסרת רישום מEvent.

לדוגמה, נניח שיש לנו את המחלקה הזאת:

1
2
3
4
public class EventRaiser
{
public event EventHandler Raised;
}

אז בתוך המחלקה נוכל לעשות עם הEvent מה שבא לנו:

1
2
3
4
5
6
7
8
9
10
11
public class EventRaiser
{
public event EventHandler Raised;
private void WhoKnew()
{
MethodInfo raisedMethod = this.Raised.Method;
this.Raised(this, EventArgs.Empty);
this.Raised = null;
}
}

מחוץ למחלקה לעומת זאת, אף שורה בפונקציה זו לא תתקמפל:

1
2
3
4
5
EventRaiser raiser = new EventRaiser();
MethodInfo raisedMethod = raiser.Raised.Method;
raiser.Raised(raiser, EventArgs.Empty);
raiser.Raised = null;
EventHandler raisedEvent = raiser.Raised;

נקבל שגיאת קימפול

The event ‘EventRaiser.Raised’ can only appear on the left hand side of += or -= (except when used from within the type ‘EventRaiser’)

כלומר, הקומפיילר מאפשר לנו רק להוסיף או להסיר רישומים לEvent.

הדבר דומה להבדל בין Property לField.

בתוך המחלקה אנחנו יכולים לעשות עם הField שלנו מה שבראש שלנו. ברגע שאנחנו מייחצנים Property, אנחנו יותר מוגבלים. למשל, אנחנו יכולים למנוע שינוי של הProperty, אלא רק לאפשר קריאה.


בכל אופן חשוב לשים לב לדברים הבאים:

בתוך המחלקה שלו, Event הוא Delegate לכל דבר. בין השאר, אפשר גם לאפס את הערך שלו, וכאשר הinstance שלנו מת אנחנו יכולים לאפס את הEvent בתוך המחלקה, כך שאין צורך לדאוג שכל מי שנרשם אלינו יסיר את הרישום וכו’.

בנוסף, Event זה בסה"כ הכמסה של הקומפיילר – כאשר אנחנו משתמשים בEvent, מאחורי הקלעים יש לנו Delegate, כך שמבחינה טכנית, אין דבר שאפשר לעשות עם Event ואי אפשר לעשות עם Delegate. טעות נפוצה היא לחשוב שDelegate מאפשר רישום רק למתודה אחת ואילו Event מאפשר רישום לכמה מתודות. זה לא נכון – גם Delegate מאפשר רישום לכמה מתודות. (בעזרת האופרטור +=, או לחלופין בעזרת שימוש בפונקציה Delegate.Combine היוצרת Delegate חדש שקורא לכל הDelegateים אחד אחרי השני)

המשך יום מלא אירועים טובים

שתף

192. Thread CurrentCuture

בהמשך לטיפים הקודמים,

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

הבעיה העיקרית בפתרונות שראינו היא שבכל קריאה לToString ולDateTime.ParseExact (או לDateTime.Parse), אנחנו צריכים לציין את הCultureInfo או את הפורמט שאנחנו מעוניינים להשתמש בו.

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

מסתבר שקיים הProperty ששמו CurrentCulture של Thread המאפשר לנו לציין עם איזה CultureInfo אנחנו מעוניינים לעבוד בThread הספציפי.

למשל,

1
2
CultureInfo usCulture = new CultureInfo("en-us", false);
Thread.CurrentThread.CurrentCulture = usCulture;

כעת אם נבצע את השורות הבאות נקבל:

1
2
3
DateTime rememberRemember = new DateTime(1997, 11, 5);
string rememberRememberString = rememberRemember.ToString();
// 11/5/1997 12:00:00 AM

ללא תלות בהגדרות המחשב שלנו, ולהפך:

1
2
3
4
5
6
7
DateTime theFifthOfNovember =
DateTime.Parse(rememberRememberString);
if (rememberRemember == theFifthOfNovember)
{
// true
}

כך נצטרך לאתחל את הCultureInfo רק במקום אחד עבור כל Thread.

למרבה הצער, אין Property דומה המאפשר לנו לאתחל את הCultureInfo עבור כל הProcess, ולכן צריך לחשוב על פתרון לשאלה היכן מתאחלים אותו.

אופציה אחת היא ליצור Factory מיוחד של Threadים שידע לאתחל לThreadים את הProperty הזה בCultureInfo המתאים, וכל פעם שמישהו יבקש Thread הוא יצטרך לעבור דרך הFactory הזה.

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

המשך יום טוב עם תרבות נוכחית טובה

שתף

191. CultureInfo

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

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

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

השגת הCultureInfo הרצוי:

1
CultureInfo israelCulture = new CultureInfo("he-il", false);

כתיבה:

1
2
3
4
DateTime rememberRemember = new DateTime(1997, 11, 5);
string rememberRememberString =
rememberRemember.ToString(israelCulture);

פרסור:

1
2
3
DateTime rememberRemember =
DateTime.Parse(rememberRememberString,
israelCulture);

שימו לב שאנחנו מעבירים false בConstructor של CultureInfo. זאת מאחר ואחרת ההגדרות של המשתמש דורסות את ההגדרות הדיפולטיות. מה זאת אומרת? נניח שהמשתמש החליט לבחור שהפורמט שהוא רוצה להציג בו את התאריך הוא פורמט לפי התאריך העברי,

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

שבוע מידע תרבותי טוב

שתף