71. Event raise extension method

הכרנו בשבוע שעבר קצת Extension Methods.

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

אחד השימושים הנחמדים לדבר הזה הוא Extension method להקפצת אירועים. בד”כ כשאנחנו מקפיצים אירועים אנחנו כותבים קוד שדומה לקוד הזה:

1
2
3
4
5
6
7
8
9
public event EventHandler<MyEventArgs> MyEvent;
private void RaiseMyEvent(MyEventArgs eventArgs)
{
if (MyEvent != null)
{
MyEvent(this, eventArgs);
}
}

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

ניתן במקום זאת ליצור Extension Method כזה:

1
2
3
4
5
6
7
8
9
10
public static void Raise<TEventArgs>(this EventHandler<TEventArgs> eventHandler,
object sender,
TEventArgs args)
where TEventArgs : EventArgs
{
if (eventHandler != null)
{
eventHandler(sender, args);
}
}

ואז להקפיצו בצורה הבאה:

1
MyEvent.Raise(this, args);

שחוסך הרבה מתודות סתמיות שרק נועדו בשביל להקפיץ אירועים.

עוד יותר מזה, אם אנחנו לא נוהגים להקפיץ אירועים כפי שציינתי עכשיו, אלא בדרך יותר מטורפת (למשל עם כל מיני Interlockים וכדומה), נצטרך לכתוב את הקוד של הקפצת האירוע רק פעם אחת 😃

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

1
MyEvent(this, args);

והשיטה שהצגנו מספיק דומה לה 😃

שבוע מורחב טוב

שתף

70. Extension methods meet lambda expressions

בהמשך לשבוע המורחב הטוב,

אחד הדברים שמסתבר שמשתלבים טוב יחד, הם Extension Methods וLambda Expressions.

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

*
**
***
****

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

דרך מעט יותר מגניבה לעשות זאת היא כך: אנחנו נגדיר Extension Method כזה:

1
2
3
4
5
6
7
8
public static void Times(this int number,
Action action)
{
for (int i = 1; i <= number; i++)
{
action();
}
}

ואף כזה:

1
2
3
4
5
6
7
8
public static void Times(this int number,
Action<int> action)
{
for (int i = 1; i <= number; i++)
{
action(i);
}
}

זהו Extension Method לint שקורא לdelegate שהוא מקבל בהתאם למספר הפעמים שהתקבל.

נשמע מסובך? בוא נראה מספר דוגמאות:

הדפסת המשפט I will do my homework עשר פעמים:

1
10.Times(() => Console.WriteLine("I will do my homework"));

הדפסת כל המספרים עד המספר 10:

1
10.Times(x => Console.WriteLine(x));

דוגמא נוספת:

ציור פירמידה בגודל 10:

1
2
3
4
5
10.Times(row =>
{
row.Times(() => Console.Write("*"));
Console.WriteLine();
});

סופ"ש מורחב טוב

שתף

69. Calling generic extension methods implicity

בהמשך לטיפ היומי של אתמול,

ראינו שאם יש לנו מתודה גנרית, ניתן להפוך אותה לExtension method:

1
public static T First<T>(this IEnumerable<T> enumerable)

ולקרוא לה כך:

1
2
3
4
5
6
7
8
9
10
11
IEnumerable<string> strings =
new string[]
{
"A new hope",
"The empire strikes back",
"The return of the Jedi"
};
string firstMovie = strings.First<string>(); // A new hope
char firstLetter = "The first letter is 'T'".First<char>();

ראינו בעבר (טיפ מספר 28) שאם יש לנו מתודה גנרית, ניתן לקרוא לה לרוב implicity, בלי ציון הפרמטרים:

1
2
3
string firstMovie = Utilities.First(strings); // A new hope
char firstLetter = Utilities.First("The first letter is 'T'");

הדבר נכון גם לגבי Extension Methods גנריים!

מאחר והקוד מתקמפל לאותו קוד נוכל לכתוב את הקוד הבא:

1
2
3
string firstMovie = strings.First(); // A new hope
char firstLetter = "The first letter is 'T'".First();

שימו לב ליופי שבדבר!

אם בפעם שעברה ראינו "שהוספנו" מתודה לכל IEnumerable<T> בלי לדעת בכלל מהו T, כעת אנחנו "הוספנו" מתודה לכל IEnumerable<T> ואנחנו בכלל לא צריכים לציין מהו T.

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

כל המגבלות שאמרנו ביום ראשון על Extension Methods חלות גם פה (אי אפשר שניים עם אותה חתימה באותו שם באותו namespace וכו’), וכן המגבלות של הקומפיילר לפענח לבדו מהו T. (ראו גם הטיפ היום מספר 28)

המשך יום מורחב טוב

שתף

68. Extension methods meet generics

בהמשך לטיפים האחרונים,

נוכל להשתמש בExtension methods גם על דברים גנריים.

למשל משהו כזה:

מתודה שמחזירה את האיבר הראשון של enumerable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static T First<T>(this IEnumerable<T> enumerable)
{
if (enumerable == null)
{
throw new NullReferenceException
("enumerable was null");
}
using (IEnumerator<T> enumerator = enumerable.GetEnumerator())
{
// Enumerable is empty.
if (!enumerator.MoveNext())
{
throw new ArgumentException
("Received an empty enumerable",
"enumerable");
}
else
{
return enumerator.Current;
}
}
}

נוכל לקרוא לה כך:

1
2
3
4
5
6
7
8
9
IEnumerable<string> strings =
new string[]
{
"A new hope",
"The empire strikes back",
"The return of the Jedi"
};
string firstMovie = strings.First<string>(); // A new hope

מה שיפה שוב זה שבעצם יצרנו extension method שעובד על כל IEnumerable<T>.

נוכל למשל גם לכתוב משהו כזה:

1
char firstLetter = "The first letter is 'T'".First<char>();

ממש מגניב.

יום מורחב טוב

שתף

67. How do extension methods work

ראינו בפעם הקודמת מה זה Extension methods.

ייתכן ועדיין לא השתכנעו למה זה טוב, אבל אנחנו נראה הרבה מאוד דוגמאות שיוכיחו לנו – זה אכן טוב.

היום נראה איך Extension Methods בעצם ממומשים.

בין C# 2.0 לC# 3.0 לא השתנה הCLR ולכן קריאה לExtension Method היא בעצם syntactic sugar למשהו שמוכר לנו:

קריאה סטטית prefixית של המשתנה.

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

את התשובה נמצא, כמובן, בעזרת הReflector:

מבט חטוף בReflector על הIL של הExtension Method שכתבנו אתמול מניב את התוצאה הבאה:

1
2
3
4
.method public hidebysig static string Reverse(string givenString) cil managed
{
.custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
}

אמנם קשה להבין מזה משהו, אבל בעצם מוצמד למתודה שלנו Attribute מהסוג System.Runtime.CompilerServices.ExtensionAttribute:

זהו Attribute שאומר לקומפיילר שהמתודה שלנו היא בעצם Extension Method.

עם זאת, אם ננסה לכתוב קוד כזה במקום מה שכתבנו:

1
2
3
4
5
6
7
8
9
10
11
12
13
[Extension]
public static string Reverse(string givenString)
{
List<char> reverseChars =
new List<char>(givenString.Length);
foreach (char currentChar in givenString)
{
reverseChars.Insert(0, currentChar);
}
return new string(reverseChars.ToArray());
}

נקבל בזמן קימפול את השגיאה הבאה:

Do not use ‘System.Runtime.CompilerServices.ExtensionAttribute’. Use the ‘this’ keyword instead.

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

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

למרבה הצער, CodeDom תומך בC# 2.0 (גם לא בצורה מלאה), לכן חשבתי שאוכל להשתמש בשיטה זו כדי להוסיף את הAttribute ולקבל קוד שמתנהג כמוExtension Methods.

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

המשך יום מורחב טוב

שתף

66. extension methods

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

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

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

היינו רוצים לכתוב בעצם משהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
public string Reverse()
{
List<char> reverseChars =
new List<char>(this.Length);
foreach (char currentChar in this)
{
reverseChars.Insert(0, currentChar);
}
return new string(reverseChars.ToArray());
}

אבל אנחנו לא יכולים לשנות את הקוד של המחלקה string.

בנוסף, אנחנו גם לא יכולים לרשת מstring ולהוסיף שם את המתודה.

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

שימו לב שהמתודה Reverse לא ניגשת לMemberים פרטיים של string ולכן נוכל ליצור פונקציה סטטית ששקולה לה:

1
2
3
4
5
6
7
8
9
10
11
12
public static string Reverse(string givenString)
{
List<char> reverseChars =
new List<char>(givenString.Length);
foreach (char currentChar in givenString)
{
reverseChars.Insert(0, currentChar);
}
return new string(reverseChars.ToArray());
}

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

1
string reversedString = Reverse("Hello world");

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

1
2
string reversedString =
StringUtilities.Reverse("Hello world");

הסתרבלנו קצת.


פה באו עם אחד הדברים המהפכניים, ולדעתי הדבר הכי מהפכני בC# 3.0, מה שנקרא Extension methods.

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

1
string reversedString = "Hello world".Reverse();

כדי לעשות את זה צריך לעשות את הדברים הבאים:

צריך להכניס את המתודה הסטטית שלנו למחלקה סטטית, למשל StringUtilities.

אחרי זה צריך להוסיף לפני הType של המשתנה שאנחנו רוצים לקרוא למתודה שלו בצורה Postfix את הkeyword ששמו this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static class StringUtilities
{
public static string Reverse(this string givenString)
{
List<char> reverseChars =
new List<char>(givenString.Length);
foreach (char currentChar in givenString)
{
reverseChars.Insert(0, currentChar);
}
return new string(reverseChars.ToArray());
}
}

כעת נוכל לקרוא למתודה בצורה postfixית:

1
string reversedString = "Hello world".Reverse();

וגם בצורה prefixית:

1
string reversedString = StringUtilities.Reverse("Hello world");

מטורף, אה?

גם אם היו לנו מספר פרמטרים יכולנו ליצור Extension method, למשל:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static string Repeat(this string givenString,
int times)
{
List<string> duplicatedStrings = new List<string>();
for (int i = 0; i < times; i++)
{
duplicatedStrings.Add(givenString);
}
return string.Join(string.Empty,
duplicatedStrings.ToArray());
}

נוכל לקרוא לה ע"י קריאה postfixית של המשתנה הראשון:

1
string repeated = "Hello world".Repeat(3);

מגניב..


קצת מגבלות:

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

בנוסף, אם יש לנו בnamespace שתי extension methods עם אותו שם, לא נוכל לקרוא בpostfix.

חוץ מזה, ניתן לכתוב extension methods רק לארגומנט הראשון שלהן, כלומר את הkeyword ששמו this נוכל לשים רק לפני המשתנה הראשון.

לבסוף, אם אנחנו רוצים להשתמש בextension method מnamespace אחר, נאלץ לעשות לו using.

בסה"כ extension methods זו דרך מאוד אלגנטית לבצע דברים שהיינו עושים עד היום במחלקות Utilities למיניהן.

שיהיה שבוע מורחב טוב

שתף

65. rethrowing exceptions

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

1
2
3
4
5
6
7
8
9
10
11
12
13
private static void Initialize()
{
try
{
LoadConfiguration();
// Do things here
}
catch (FileNotFoundException ex)
{
// Write to log.
throw ex;
}
}

עם מתודה מתאימה:

1
2
3
4
5
6
7
private static void LoadConfiguration()
{
using (StreamReader reader = new StreamReader("MyConfiguration.txt"))
{
// Do stuff here
}
}

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

הקוד לכאורה נראה תקין, אבל מתחבאת פה תקלה.

אם נסתכל הstack trace בexception שעף מהcatch block נראה exception שמתחיל מהמתודה Initialize:

at Initialize()
at Main(String[] args)

ולא מLoadConfiguration.

הדרך הנכונה היא לזרוק את הexception מחדש היא בצורה הבאה:

1
2
3
4
5
6
7
8
9
10
11
12
13
private static void Initialize()
{
try
{
LoadConfiguration();
// Do things here
}
catch (FileNotFoundException ex)
{
// Write to log.
throw;
}
}

שימו לב להבדל: השתמשנו בthrow בלי לציין את הexception שאנחנו זורקים.

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

הstack trace כעת יראה כך:

at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath(
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize)
at System.IO.StreamReader..ctor(String path)
at LoadConfiguration()
at Initialize()
at Main(String[] args)

כמו שצריך.

המשך יום גשום טוב

שתף

64. finalize Method

[נכתב ע”י מתן אופנהיים]

בהמשך לשבוע שחרור משאבים (Dispose ושות’),

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

בנוסף, יכול להיות שהאובייקט שלנו ינוקה על ידי הGC, לפני שמתודת Dispose נקראה!

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

באופן כללי, המתכנת מממש פונקצית Finalize בעצמו (הsyntax יפורט בהמשך), שאחראית על שחרור משאבים כלשהם (לרוב unmanaged).

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

מנגנון הGC מנהל thread שאחראי על קריאה לכל פונקציות הFinalize של אובייקטים שנחשבים ללא נגישים.

הבהרות :

Syntax:

1
2
3
4
5
6
7
class Car
{
~Car() // Destructor
{
// Cleanup statements...
}
}

בפועל , הפונקציה ~Car() מתורגמת לקוד הבא:

1
2
3
4
5
6
7
8
9
10
11
protected override void Finalize()
{
try
{
// Cleanup statements...
}
finally
{
base.Finalize();
}
}

הערה: מומלץ לממש Finalizer רק במידת הצורך מאחר ומימוש Finalizer מגדיל משמעותית את הזמן שיקח לאובייקט להאסף ע"י הGC.

יום לא גשום טוב

שתף

63. using statement

בפעמים הקודמות הכרנו קצת את IDisposable ואת finally.

ראינו שהדרך המומלצת להשתמש בIDisposable היא באמצעות בלוק של try-finally.

למרבה הצער, לא ממש כיף לכתוב קוד כזה:

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
FileStream myStream =
new FileStream("AReallyGreatFile.txt",
FileMode.Open,
FileAccess.Read);
try
{
StreamReader myStreamWriter =
new StreamReader(myStream);
try
{
string content =
myStreamWriter.ReadToEnd();
}
finally
{
if (myStreamWriter != null)
{
myStreamWriter.Dispose();
}
}
}
finally
{
if (myStream != null)
{
myStream.Dispose();
}
}

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

1
2
3
4
5
6
7
8
9
FileStream myStream =
new FileStream("AReallyGreatFile.txt",
FileMode.Open,
FileAccess.Read);
// Do stuff
myStream.Dispose();
myStream.Flush(); // Exception

את הבעיות האלה אפשר לפתור בצורה אלגנטית באמצעות שימוש בkeyword נחמד שנקרא using

הkeyword בעצם משתמש בtry-finally כמו שראינו בפעם הקודמת שמומלץ לעשות.

הקוד שראינו מעלה נראה כך כעת:

1
2
3
4
5
6
7
8
9
10
11
12
using (FileStream myStream =
new FileStream("AReallyGreatFile.txt",
FileMode.Open,
FileAccess.Read))
{
using (StreamReader myStreamWriter =
new StreamReader(myStream))
{
string content =
myStreamWriter.ReadToEnd();
}
}

ויש לנו הגנה מגישה לאובייקט אחרי Dispose:

1
2
3
4
5
6
7
8
9
using (FileStream myStream =
new FileStream("AReallyGreatFile.txt",
FileMode.Open,
FileAccess.Read))
{
// Do stuff
}
myStream.Flush(); // Doesn't compile

יום גשום מצוין

שתף

62. IDisposable interface

מדי פעם אנחנו נתקלים בinterface ששמו IDisposable.

מדובר בממשק שנראה ככה:

1
2
3
4
public interface IDisposable
{
void Dispose();
}

ויש לו מתודה אחת שקוראים לה Dispose.

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

באופן כללי, הממשק הומצא כדי לנקות משאבים שהם unmanaged (למשל גישה לקבצים, פקדים וכו’) –

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

המטרה של הממשק היא לנקות את המשאבים הunmanaged באופן ייזום. מחזיק של אובייקט יכול לקרוא לDispose כאשר אין לו צורך באובייקט יותר.


מימוש:

מומלץ לממש במידה והאובייקט שלנו משתמש בעצמו במשאבים שהם unmanaged, (קבצים, פקדים, Threadים וכו’), גם אם הוא משתמש בעטיפות שלהם.

בנוסף, מומלץ לממש אם ברצונכם לאפשר לקרוא באופן ייזום לניקיון של האובייקט.

שימוש:

מומלץ להשתמש בממשק באמצעות בלוק של try וfinally, למשל:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
OleDbCommand command = new OleDbCommand();
OleDbTransaction transaction = command.Transaction;
try
{
transaction.Begin();
// Do risky things here.
transaction.Commit();
}
finally
{
if (transaction != null)
{
transaction.Dispose();
}
}

יום גשום טוב

שתף