CallerMemberName, CallerFilePath, CallerLineNumber Attributes

אחת היכולות שנוספו בC# 5.0 היא יכולת לא כל כך מפורסמת והיא הבאה:

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

  • CallerMemberNameAttribute - זהו Attribute שאם נשים מעל פרמטר מסוג string, נקבל את שם הMember שקרא למתודה שלנו (לקונבנציית השמות, ראו את הפירוט המלא בMSDN)
  • CallerFilePathAttribute- אם נשים Attribute זה מעל פרמטר מסוג string, נקבל את שם הקובץ (הcs) שבו נכתבה השורה שקוראת למתודה שלנו
  • CallerLineNumberAttribute- אם נשים Attribute זה מעל פרמטר מסוג int, נקבל את השורה בקובץ הנ”ל

השימוש בAttributeים הוא באופן הבא:

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

דוגמה:

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
38
39
40
41
42
43
44
45
46
47
48
49
public class Person
{
private string mName;
private int mAge;
public string Name
{
get
{
Log();
return mName;
}
set
{
Log();
mName = value;
}
}
public int Age
{
get
{
Log();
return mAge;
}
set
{
Log();
mAge = value;
}
}
public void Save()
{
Log();
}
private void Log([CallerMemberName] string memberName = null,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine("Called by member: {0}, " +
"file path: {1}, line number: {2}",
memberName,
filePath,
lineNumber);
}
}

כעת נקרא למתודות ולProperties ונראה מה קורה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Person person = new Person();
person.Age = 18;
// Called by member: Age, file path: c:\TipSharp\Program.cs, line number: 39
person.Name = "Danny";
// Called by member: Name, file path: c:\TipSharp\Program.cs, line number: 25
int age = person.Age;
// Called by member: Age, file path: c:\TipSharp\Program.cs, line number: 34
string name = person.Name;
// Called by member: Name, file path: c:\TipSharp\Program.cs, line number: 20
person.Save();
// Called by member: Save, file path: c:\TipSharp\Program.cs, line number: 46

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

אם נסתכל על הקוד שקימפלנו בReflector/ILSpy נראה כי הקומפיילר פשוט שתל את הערכים האלה hard-coded בקריאות למתודות:

ככה נראה הPerson הdecompiled (לC# 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Person
{
private string mName;
private int mAge;
public string Name
{
get
{
this.Log("Name", "c:\\TipSharp\\Program.cs", 20);
return this.mName;
}
set
{
this.Log("Name", "c:\\TipSharp\\Program.cs", 25);
this.mName = value;
}
}
public int Age
{
get
{
this.Log("Age", "c:\\TipSharp\\Program.cs", 34);
return this.mAge;
}
set
{
this.Log("Age", "c:\\TipSharp\\Program.cs", 39);
this.mAge = value;
}
}
public void Save()
{
this.Log("Save", "c:\\TipSharp\\Program.cs", 46);
}
private void Log([CallerMemberName] string memberName = null,
[CallerFilePath] string filePath = null,
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine("Called by member: {0}, " +
"file path: {1}, line number: {2}",
memberName,
filePath,
lineNumber);
}
}

הערה: שימו לב שהFilePath שאנחנו מקבלים הוא מה שהקומפיילר רואה כשהוא מקמפל את הקריאה שלנו למתודה. מאחר ואנחנו מקמפלים בד"כ על מכונות בילדים, אם אתם מאיזשהי סיבה רוצים להשתמש בCallerFilePathAttribute, תדאגו לחלץ מהPath הזה איזשהו path רלטיבי או משהו שלא תלוי באיפה התוכנה התקמפלה (למשל, בעזרת המחלקה Path).

כעקרון אני מעודד לא להשתמש בCallerFilePath וCallerLineNumber. באשר לCallerMemberName, הוא כן שימושי ואני אכתוב על זה בהמשך.

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

שתף