250. Implementing a fluent syntax - part 3

בהמשך לפעמים הקודמות, נניח שהגדרנו Fluent Syntax חביב ויצרנו Extension Method מתאים לאובייקט שלנו כדי שהמשתמשים שלנו יוכלו להשתמש בו.

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

1
2
3
4
Type GetType();
int GetHashCode();
string ToString();
bool Equals(object other);

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

ניצור ממשק

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IFluentSyntax
{
[EditorBrowsable(EditorBrowsableState.Never)]
Type GetType();
[EditorBrowsable(EditorBrowsableState.Never)]
int GetHashCode();
[EditorBrowsable(EditorBrowsableState.Never)]
string ToString();
[EditorBrowsable(EditorBrowsableState.Never)]
bool Equals(object other);
}

הAttribute ששמו EditorBrowsable מציין האם מתודה או Property יוצגו בEditor.

כעת אם נירש מהממשק הזה:

1
2
3
4
public interface ITableDefinitionSyntax : IFluentSyntax
{
// ...
}

לא יוצגו המתודות האלה יותר בIntellisense.


הפאץ’ הזה לקוח מתוך Ninject. הוא מיוחס לDaniel Cazzulino.


סייגים: (כדי שלא תגידו שהטיפ היומי הוליך אתכם שולל)

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

אבל אם נוסיף Reference לפרויקט שלנו מפרויקט אחר, לא נראה אותן.

בנוסף, אם מותקן לכם ReSharper, הוא כן יציג לכם את המתודות האלה 😃

סוף שבוע צף טוב!

שתף

249. Implementing a fluent syntax part 2.5

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

אני מרגיש שקצת רימיתי בכתיבה של הInterfaceים של הSyntax, כי לא כל כך ברור איך חשבתי על הקשרים הנכונים בין הSyntaxים.

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

לאחר מכן חשבתי על זה ואפשר להסתכל על הבעיה בצורה אחרת:

ננסה לממש את מה שכתבתי בסוף הטיפ הקודם – שאפשר לקרוא לWithDefaultValue לכל היותר פעם אחת לעמודה, ולMakePrimaryKey פעם אחת לטבלה.

אנחנו מעוניינים שתמיד הפעולה הראשונה שתקרה היא WithColumn, לאחר מכן אנחנו מרשים שיקרו הפעולות WithColumn, WithDefaultValueוMakePrimaryKey, אבל עם הסייגים הבאים:

  • אפשר לקרוא לMakePrimaryKey רק פעם אחת
  • בין כל שני WithColumn אפשר לקרוא לWithDefaultValue רק פעם אחת

מדובר בעצם בשפה הנוצרת מעל הא”ב

$ { \text{WithColumn}, \text{MakePrimaryKey}, \text{WithDefaultValue} } $

עם החוקים שרשמנו מעלה.

אם נסמן $ a = \text{WithColumn}$, $ b =\text{MakePrimaryKey}$, $ c =\text{WithDefaultValue}$, אז השפה היא כל המילים שמתחילות ב$ a$, מכילות $ b$ לכל היותר פעם אחת, ומכילות $ c$ לכל היותר פעם אחת בין כל שני $ a$ים.

לשפה הזו אפשר לבנות אוטומט מתאים:

249_1.png

כאשר כל המצבים פה הם מצבים מקבלים, ובמעברים שלא מופיעה בהם אות מסוימת, מדובר במעבר למצב מלכודת, כלומר מצב לא חוקי. (כלומר אם כתבנו $ b $ פעמיים במילה או $ c$ יותר מפעם אחת בין שני $ a $ים, לא נוכל לתקן את המילה)

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

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 interface IQ1
{
IQ2<T> WithColumn<T>(string name);
}
public interface IQ2<T>
{
IQ2<S> WithColumn<S>(string name);
IQ3<T> MakePrimaryKey();
IQ4 WithDefaultValue(T value);
}
public interface IQ3<T>
{
IQ5 WithDefaultValue(T value);
IQ6<S> WithColumn<S>(string name);
}
public interface IQ4
{
IQ5 MakePrimaryKey();
IQ2<S> WithColumn<S>(string name);
}
public interface IQ5
{
IQ6<T> WithColumn<T>(string name);
}
public interface IQ6<T>
{
IQ5 WithDefaultValue(T value);
IQ6<S> WithColumn<S>(string name);
}

איך התרגום התבצע? פשוט יצרנו ממשק לכל מחלקה. כעת דאגנו ששימוש בפונקציה המתאימה (כלומר הוספת אות מהא"ב), יוביל אותנו למצב המתאים לפי האוטומט.

לדוגמה, ב$ Q_3 $ יש מעבר ל$ Q_5 $ אם מקבלים $ c $, אבל מעבר ל$ Q_6 $ אם מקבלים $ a $.

לכן בממשק IQ3<T> כשאנחנו קוראים לפונקציהWithDefaultValue (שאותה מייצגת האות $ c $), אנחנו דואגים להחזיר IQ5. לעומת זאת, כשאנחנו קוראים לWithColumn (שאותה מייצגת האות $ a $), אנחנו דואגים להחזיר IQ6.

הדבר הזה מאפשר לנו לאכוף את מה שרצינו: בהינתן הExtension Method הבא:

1
2
3
4
public static IQ1 AddTable(this DataSet dataSet, string name)
{
// ...
}

כל החוקים שרצינו לאכוף נאכפים:

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
36
37
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(0).MakePrimaryKey().
WithColumn<string>("NAME").
WithDefaultValue("John Doe");
// Compiles
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(1).
WithDefaultValue(0).
MakePrimaryKey().
WithColumn<string>("NAME").
WithDefaultValue("John Doe");
// Doesn't Compile
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(1).
MakePrimaryKey().
WithColumn<string>("NAME").
MakePrimaryKey().
WithDefaultValue("John Doe");
// Doesn't Compile
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(1).
MakePrimaryKey().
WithDefaultValue(0).
WithColumn<string>("NAME").
WithDefaultValue("John Doe");
// Doesn't Compile

ממש מגניב!

המשך יום צף טוב!

שתף

248. Implementing a fluent syntax part 2

אז פעם קודמת ראינו איך אפשר להתחיל לממש Fluent syntax.

אחת הבעיות שיש עם המימוש הזה, הוא שהSyntax הוא חסר זכרון.

מה זאת אומרת? נניח שיש לנו באמת את האפשרות להפוך איזשהי עמודה להיות PrimaryKey. לא נרצה שתהיה לנו אפשרות להפוך את שאר העמודות להיות PrimaryKey. עם זאת, במימוש הנוכחי, נוכל לקרוא לMakePrimaryKey כמה פעמים שנרצה:

1
2
3
4
5
6
7
8
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
MakePrimaryKey().
WithColumn<string>("NAME").
WithDefaultValue("John Doe").
MakePrimaryKey();
// Compiles

נוכל לפתור זאת ע"י הפיכת הSyntax להיות בעל זכרון. הדבר הזה כרוך ביצירת הרבה ממשקים לכל אחד מהמצבים:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface ITableDefinitionSyntax
{
ITableOrColumnDefinitionSyntax<T> WithColumn<T>(string name);
}
public interface IColumnDefaultValueDefinitionSyntax<T>
{
ITableOrColumnDefinitionSyntax<T> WithDefaultValue(T value);
}
public interface ITableOrColumnDefinitionSyntax<T> : ITableDefinitionSyntax, IColumnDefaultValueDefinitionSyntax<T>
{
}

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

באשר לPrimary Key: נוסיף ממשקים מתאימים:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface IColumnMakePrimaryKeyDefinitionSyntax<T>
{
ITableOrColumnDefinitionSyntax<T> MakePrimaryKey();
}
public interface IColumnDefaultValueMakePrimaryKeyDefinitionSyntax<T> : IColumnMakePrimaryKeyDefinitionSyntax<T>
{
ITableOrColumnMakePrimaryKeyDefinitionSyntax<T> WithDefaultValue(T value);
}
public interface ITableWithPrimaryKeyDefinitionSyntax
{
ITableOrColumnMakePrimaryKeyDefinitionSyntax<T> WithColumn<T>(string name);
}
public interface ITableOrColumnMakePrimaryKeyDefinitionSyntax<T> : IColumnDefaultValueMakePrimaryKeyDefinitionSyntax<T>, ITableWithPrimaryKeyDefinitionSyntax
{
}

יש פה קצת רקורסיה, אבל נראה לי שנסתדר.

בהנחה שיש לנו Extension Method מתאים:

1
2
3
4
public static ITableWithPrimaryKeyDefinitionSyntax AddTable(this DataSet dataSet, string name)
{
// ...
}

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

1
2
3
4
5
6
7
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(0).
MakePrimaryKey().
WithColumn<string>("NAME").
WithDefaultValue("John Doe");

אבל ברגע שהפעלנו את הפונקציה MakePrimaryKey, לא נוכל להפעילה יותר!

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
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(0).
MakePrimaryKey().
WithColumn<string>("NAME").
WithDefaultValue("John Doe").
MakePrimaryKey(); // Won't compile
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(0).
MakePrimaryKey().
MakePrimaryKey(). // Won't compile
WithColumn<string>("NAME").
WithDefaultValue("John Doe");
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
WithDefaultValue(0).
MakePrimaryKey().
WithColumn<string>("NAME").
MakePrimaryKey(). // Won't compile
WithDefaultValue("John Doe");

מגניב ביותר! 😃

החיסרון הוא, כמובן, שצריך ליצור יותר Interfaceים ולהרכיב אותם בצורה הנכונה. אבל אם אנחנו רוצים למנוע שימוש יותר מפעם אחת במשהו, אז זה בהחלט שווה את ההשקעה. (לדוגמה אם חשוב לנו שלא יהיה אפשר להגדיר את Default Value פעמיים, נוכל לטפל בזה ע"י הוספת Interfaceים מתאימים ש"זוכרים" גם את הסטאטוס של MakePrimaryKey וגם את הסטאטוס של WithDefaultValue)

המשך יום צף טוב!

שתף

247. Implementing a fluent syntax part 1

בהמשך לפעם הקודמת,

נניח ואנחנו רוצים לממש Fluent Syntax משלנו.

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

לצורך העניין ניצור ממשק המייצג פעולות על DataTable:

איזה פעולות יש על DataTable? נניח שנסתפק בהוספת עמודות והגדרתן:

1
2
3
4
public interface ITableDefinitionSyntax
{
ITableOrColumnDefinitionSyntax<T> WithColumn<T>(string name);
}

כעת מהו ITableOrColumnDefinitionSyntax? זהו ממשק המייצג מה אנחנו יכולים לעשות על טבלה או על Column שלה.

איזה פעולות נשים בו? נשים למשל פעולה של להפוך לPrimaryKey, ופעולה של קביעת ערך דיפולטי:

1
2
3
4
5
public interface ITableOrColumnDefinitionSyntax<T> : ITableDefinitionSyntax
{
ITableOrColumnDefinitionSyntax<T> MakePrimaryKey();
ITableOrColumnDefinitionSyntax<T> WithDefaultValue(T value);
}

ניצור מימושים פשוטים:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
internal class TableDefinitionSyntax : ITableDefinitionSyntax
{
protected readonly DataTable mTable;
public TableDefinitionSyntax(DataTable table)
{
mTable = table;
}
public ITableOrColumnDefinitionSyntax<T> WithColumn<T>(string name)
{
DataColumn column = mTable.Columns.Add(name,typeof(T));
return new TableOrColumnDefinitionSyntax<T>(mTable, column);
}
}

ולITableOrColumnDefinitionSyntax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
internal class TableOrColumnDefinitionSyntax<T> : TableDefinitionSyntax, ITableOrColumnDefinitionSyntax<T>
{
private readonly DataColumn mColumn;
public TableOrColumnDefinitionSyntax(DataTable table, DataColumn column) : base(table)
{
mColumn = column;
}
public ITableOrColumnDefinitionSyntax<T> MakePrimaryKey()
{
mTable.PrimaryKey = newDataColumn[]{mColumn};
return this;
}
public ITableOrColumnDefinitionSyntax<T> WithDefaultValue(T value)
{
mColumn.DefaultValue = value;
return this;
}
}

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

מה אפשר לעשות עם מה שעשיתי עד כה? אפשר ליצור Extension Method לDataSet שיתן לנו Syntax קצת Fluentי:

1
2
3
4
5
public static ITableDefinitionSyntax AddTable(this DataSet dataSet,string name)
{
DataTable added = dataSet.Tables.Add(name);
return new TableDefinitionSyntax(added);
}

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

1
2
3
4
5
6
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
MakePrimaryKey().
WithColumn<string>("NAME").
WithDefaultValue("John Doe");

ממש קסם 😃

המשך יום צף טוב!

שתף

246. Fluent syntax

סדרת הטיפים הקרובה עוסקת במה שנקרא Fluent syntax.

לפעמים אנחנו צריכים “לקנפג” איזשהו אובייקט. לפעמים קינפוג זה הוא לא כל כך פשוט, למשל יצירת DataSet עם שתי טבלאות וRelation בין שני DataTableים נראה בערך כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
DataSet dataSet = new DataSet();
DataTable employers = dataSet.Tables.Add("EMPLOYER");
employers.Columns.Add("NAME", typeof (string));
employers.Columns.Add("DEPARTMENT_FK", typeof(int));
DataTable departments = dataSet.Tables.Add("DEPARTMENT");
departments.Columns.Add("PK", typeof (int));
departments.Columns.Add("NAME", typeof(string));
departments.PrimaryKey = new DataColumn[] {departments.Columns["PK"]};
DataRelation employerToDepartment =
new DataRelation("EMPLOYER_TO_DEPARTMENT",
departments.Columns["PK"],
employers.Columns["DEPARTMENT_FK"]);
employers.ParentRelations.Add(employerToDepartment);

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

Fluent Syntax (או Fluent Interface) הוא Syntax הבא להפוך את הקוד לקריא וכיפי יותר.

איך זה נעשה? הרעיון הוא שרשור קריאה למתודות אחת אחרי השנייה, בצורה שהיא Self-reference.

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

1
2
3
4
5
6
7
8
9
10
11
12
dataSet.
AddTable("DEPARTMENT").
WithColumn<int>("PK").
MakePrimaryKey().
WithColumn<string>("NAME").
AddTable("EMPLOYER").
WithColumn<string>("NAME").
WithColumn<int>("DEPARTMENT_FK").
RelatedTo("DEPARTMENT").
WithParentKey("PK").
WithChildKey("DEPARTMENT_FK").
Named("EMPLOYER_TO_DEPARTMENT");

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


איפה נתקלנו בעבר בFluent Syntax? בLINQ!

(ראו טיפים מספר 91-95)

תזכורת: אפשר לכתוב שאילתות ע"י שרשור Extension Methods:

1
2
3
IEnumerable<string> childrenNames =
family.Where(person => person.Age <= 12)
.Select(person => person.FirstName);

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

בהמשך נראה איך אנחנו יכולים לבנות Fluent Syntax משלנו 😃

המשך יום צף טוב!

שתף

245. Attributes and parameters

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

זה מתבצע כך:

1
2
3
4
public static bool MyMethod([MyParameter]int x, [MyParameter]int y)
{
// ...
}

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

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

בפועל השימושי העיקרי בFeature זה הוא של הFramework. למשל, בMarshaling יש Attribute בשם Out המציין שפרמטר מסוים צריך להסתרלז לפרמטר בחזרה מהפונקציה.

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

סופ"ש בעל סגולה טוב.

שתף

244. How default arguments values work

מהטיפ הקודם, אפשר היה לחשוב שהמימוש של Default arguments values הוא ע”י יצירת מספיק Overloadים.

כלומר שאם נציין Default Values לפרמטרים שלנו, אז יווצרו הרבה מאוד Overloadים (באופן מדויק – 2 בחזקת מספר הפרמטרים שיש להם Default).

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

1
2
3
4
5
private static void UpdateRows(IEnumerable<DataRow> rows,
TimeSpan timeOut,
bool includeInserts = true,
bool includeRemovals = true,
bool generatePks = true)

עם הקריאה:

1
UpdateRows(rows, TimeSpan.FromSeconds(5), includeRemovals: false);

נראה את הקוד הבא:

1
2
bool temp00000 = false;
UpdateRows(rows, TimeSpan.FromSeconds(5.0), true, temp00000, true);

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

זו אחת הסיבות שהם צריכים להיות Compile-time, כי הם בעצם Constים שמוכנסים באופן דיפולטי לכל הפונקציות. (ראו גם טיפ מספר 14)

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

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

שתף

243. Default arguments values

בהמשך לפעם קודמת,

נניח שיש לנו פונקציה כזאת:

1
2
3
4
5
private static void UpdateRows(IEnumerable<DataRow> rows,
TimeSpan timeOut,
bool includeInserts,
bool includeRemovals,
bool generatePks)

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

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

למשל:

1
2
3
4
5
6
7
private static void UpdateRows(IEnumerable<DataRow> rows,
TimeSpan timeOut,
bool includeRemovals,
bool generatePks)
{
UpdateRows(rows, timeOut, true, includeRemovals, generatePks);
}

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

לא באמת ניצור 16 Overloadים לכל הפרמטרים האופציונאליים.

בפועל מה שעושים זה מייצרים 2-4 Overloadים לפונקציה, ואם המשתמש רוצה משהו אחר, הוא צריך לציין את כל הפרמטרים (להשתמש בOverload הארוך)…

במקום זאת, בC# 4.0 נוספה תמיכה בציון הערך הדיפולטי של פרמטר של פונקציה. זה נעשה כך:

1
2
3
4
5
private static void UpdateRows(IEnumerable<DataRow> rows,
TimeSpan timeOut,
bool includeInserts = true,
bool includeRemovals = true,
bool generatePks = true)

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

1
UpdateRows(rows, TimeSpan.FromSeconds(5));

ברור לאיזה פרמטרים התכוונו, פשוט לכולם.

אבל במקרה הזה:

1
UpdateRows(rows, TimeSpan.FromSeconds(5), true, false);

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

אם אנחנו רוצים בכל זאת לציין רק פרמטרים ספציפיים, נוכל בעזרת Named Arguments:

1
UpdateRows(rows,TimeSpan.FromSeconds(5), includeRemovals: false);

אם נרצה להשתמש בDefault arguments values נצטרך שהפרמטרים האלה יופיעו אחרונים בחתימה של הפונקציה.

כמו כן, הערכים של טיפוסים אלה צריך להיות Compile-time (ראו גם טיפ מספר 167 להסבר על המושג)

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

שתף

242. Named Arguments

Feature נוסף שהגיע אלינו רק בC# 4.0 הוא Feature שמאפשר לקוד שלנו להיות קריא יותר:

שמו הוא Named Arguments.

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

נסתכל למשל על הקריאה הבאה:

1
UpdateRows(rows, true, true, false, TimeSpan.FromSeconds(10));

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

בC# 4.0 נוכל לציין מה הפרמטרים שנשלחו לפונקציה:

1
2
3
4
5
UpdateRows(rows: rows,
includeInserts: true,
includeRemovals: true,
generatePks: false,
timeOut: TimeSpan.FromSeconds(10));

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

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

1
2
3
4
5
UpdateRows(includeInserts: true,
rows: rows,
generatePks: false,
includeRemovals: true,
timeOut: TimeSpan.FromSeconds(10));

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

המשך יום קריא יותר טוב!

שתף

241. Dynamic and binary operations

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

1
2
3
4
5
public static T Average<T>(T first, T second)
where T : IAddable<T>
{
return (first + second)/2;
}

למרבה הצער הקוד הזה לא מתקמפל ובטח גם לא עובד.

הסיבה לכך היא שאי אפשר להכריח Operator Overload בממשק. (ראו גם טיפ מספר 196)

עד Framework 4.0 לא היה אפשר לפתור את זה בצורה פשוטה.

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

נוכל לכתוב את הקוד הבא:

1
2
3
4
public static dynamic Average(dynamic first, dynamic second)
{
return (first + second) / 2;
}

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

הבעיה פה שזה ממש לא Compile safe, ולמשל הקוד הבא גם יתקמפל:

1
dynamic invalidAverage = Average("Text", 2);

למרות שברור שזה לא אמור להתקמפל.

אפשר למנוע את זה ע"י שימוש בGenerics:

1
2
3
4
5
6
public static T Average<T>(T first, T second)
{
dynamic firstNumber = first;
dynamic secondNumber = second;
return (firstNumber + secondNumber) / 2;
}

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

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

המשך יום דינאמי טוב

שתף