Introduction
When developing complex .NET applications sometimes you need to find out the details about the caller of a method. .NET Framework 4.5 introduces what is known as Caller Info Attributes, a set of attributes that give you the details about a method caller. Caller info attributes can come in handy for tracing, debugging and diagnostic tools or utilities. This article examines what Caller Info Attributes are and how to use them in a .NET application.
Overview of Caller Info Attributes
Caller Info Attributes are attributes provided by the .NET Framework (System.Runtime.CompilerServices) that give details about the caller of a method. The caller info attributes are applied to a method with the help of optional parameters. These parameters don't take part in the method signature, as far as calling the method is concerned. They simply pass caller information to the code contained inside the method. Caller info attributes are available to C# as well as Visual Basic and are listed below:
Caller Info Attribute
|
Description
|
CallerMemberName
|
This attribute gives you the name of the caller as a string. For methods, the respective method names are returned whereas for constructors and finalizers strings ".ctor" and "Finalizer" are returned.
|
CallerFilePath
|
This attribute gives you the path and file name of the source file that contains the caller.
|
CallerLineNumber
|
This attribute gives you the line number in the source file at which the method is called.
|
A common use of these attributes will involve logging the information returned by these attributes to some log file or trace.
Using Caller Info Attributes
Now that you know what Caller Info Attributes are, let's create a simple application that shows how they can be used. Consider the Windows Forms application shown below:
The above application consists of two Visual Studio projects - a Windows Forms project that contains a form as shown above and a Class Library project that contains a class called Employee. As you might have guessed the Windows Form accepts EmployeeID, FirstName and LastName and calls AddEmployee() method of the Class Library. Though the application doesn't do any database INSERTs for the sake of illustrating Caller Info Attributes this setup is sufficient.
The Employee class that resides in the Class Library project is shown below:
1. public class Employee
2. {
3. public Employee([CallerMemberName]string sourceMemberName = "",
4. [CallerFilePath]string sourceFilePath = "",
5. [CallerLineNumber]int sourceLineNo = 0)
6. {
7. Debug.WriteLine("Member Name : " + sourceMemberName);
8. Debug.WriteLine("File Name : " + sourceFilePath);
9. Debug.WriteLine("Line No. : " + sourceLineNo);
10. }
11.
12. private int intEmployeeID;
13. public int EmployeeID
14. {
15. get
16. {
17. return intEmployeeID;
18. }
19. set
20. {
21. intEmployeeID = value;
22. }
23. }
24.
25. private string strFirstName;
26. public string FirstName
27. {
28. get
29. {
30. return strFirstName;
31. }
32. set
33. {
34. strFirstName = value;
35. }
36. }
37.
38. private string strLastName;
39. public string LastName
40. {
41. get
42. {
43. return strLastName;
44. }
45. set
46. {
47. strLastName = value;
48. }
49. }
50.
51. public string AddEmployee([CallerMemberName]string sourceMemberName="",
52. [CallerFilePath]string sourceFilePath="",
53. [CallerLineNumber]int sourceLineNo=0)
54. {
55. Debug.WriteLine("Member Name : " + sourceMemberName);
56. Debug.WriteLine("File Name : " + sourceFilePath);
57. Debug.WriteLine("Line No. : " + sourceLineNo);
58. //do database INSERT here
59. return "Employee added successfully!";
60. }
61.
The Employee class is quite simple. It contains a constructor, a method named AddEmployee() and three properties, viz. EmployeeID, FirstName and LastName. The caller info attributes are used in the constructor and AddEmployee() method. Notice how the caller info attributes are used. To use any of the caller info attributes you need to declare optional parameters and then decorate them with the appropriate attributes. In the above example the code declares three optional parameters, viz. sourceMemberName, sourceFilePath and sourceLineNo. Note that sourceLineNo is an integer parameter since the [CallerLineNumber] attribute gives a numeric result. The optional parameters are assigned some default values. These values are returned in case there is no caller information. Inside the constructor and AddMethod() the code simply outputs the parameter values to the Output window using Debug.WriteLine() statements.
The Employee class thus created is used by the Windows Forms application as follows:
1. private void button1_Click(object sender, EventArgs e)
2. {
3. Employee emp = new Employee();
4. emp.EmployeeID = int.Parse(textBox1.Text);
5. emp.FirstName = textBox2.Text;
6. emp.LastName = textBox3.Text;
7. MessageBox.Show(emp.AddEmployee());
8. }
The Click event handler of the Add Employee button simply creates a new instance of the Employee class, assigns property values and calls the AddEmployee() method.
If you run the Windows Forms application and see the Output window you should see this:
The Output window shows the caller information
As you can see the Output window shows the caller information as expected.
Using [CallerMemberName] with INotifyPropertyChanged Interface
Though the primary use of caller info attributes is in debugging and tracing scenarios, you can use the [CallerMemberName] attribute to avoid using hard-coding member names. One such scenario is when your class implements the INotifyPropertyChanged interface. This interface is typically implemented by data bound controls and components and is used to notify the user interface that a property value has changed. This way the UI can refresh itself or do some processing. To understand the problem posed by hard-coding property names see the modified Employee class below:
1. public class Employee:INotifyPropertyChanged
2. {
3. public event PropertyChangedEventHandler PropertyChanged;
4.
5. private int intEmployeeID;
6. public int EmployeeID
7. {
8. get
9. {
10. return intEmployeeID;
11. }
12. set
13. {
14. intEmployeeID = value;
15. if (PropertyChanged != null)
16. {
17. PropertyChangedEventArgs evt = new PropertyChangedEventArgs("EmployeeID");
18. this.PropertyChanged(this, evt);
19. }
20. }
21. }
22.
23. private string strFirstName;
24. public string FirstName
25. {
26. get
27. {
28. return strFirstName;
29. }
30. set
31. {
32. strFirstName = value;
33. if (PropertyChanged != null)
34. {
35. PropertyChangedEventArgs evt = new PropertyChangedEventArgs("FirstName");
36. this.PropertyChanged(this, evt);
37. }
38. }
39. }
40.
41. private string strLastName;
42. public string LastName
43. {
44. get
45. {
46. return strLastName;
47. }
48. set
49. {
50. strLastName = value;
51. if (PropertyChanged != null)
52. {
53. PropertyChangedEventArgs evt = new PropertyChangedEventArgs("LastName");
54. this.PropertyChanged(this, evt);
55. }
56. }
57. }
58.
59. public string AddEmployee([CallerMemberName]string sourceMemberName="",
60. [CallerFilePath]string sourceFilePath="",
61. [CallerLineNumber]int sourceLineNo=0)
62. {
63. ...
64. }
65. }
The Employee class now implements INotifyPropertyChanged interface. Whenever a property value is assigned it raises PropertyChanged event. The caller (Windows Forms in this case) can handle the PropertyChanged event and be notified whenever a property changes. Now the problem is that inside the property set routines the property names are hard-coded. If you ever change the property names you need to ensure that all the hard-coded property names are also changed accordingly. This can be cumbersome for complex class libraries. Using the [CallerMemberName] attribute you can avoid this hard-coding. Let's see how.
To use the [CallerMemberName] attribute to avoid hard-coding the property names you need to do a bit more work. You need to create a generic helper method that internally assigns the property values. The following code shows how this can be done:
1. protected bool SetPropertyValue<T>(ref T varName, T propValue, [CallerMemberName] string propName = null)
2. {
3. varName = propValue;
4. if (PropertyChanged != null)
5. {
6. PropertyChangedEventArgs evt = new PropertyChangedEventArgs(propName);
7. this.PropertyChanged(this, evt);
8. Debug.WriteLine("Member Name : " + propName);
9. }
10. return true;
11. }
The SetPropertyValue() method uses only the [CallerMemberName] attribute. It takes three parameters. The first reference parameter is the variable that holds a property value (strFirstName for example). The second parameter is the new property value being assigned to the property. Finally, the third optional parameter supplies the caller member name. Inside the SetPropertyValue() method you assign the property value to the variable, raises the PropertyChanged event and calls Debug.WriteLine() as before.
Now, you need to call the SetPropertyValue() method inside the property set routines as shown below:
1. private string strFirstName;
2. public string FirstName
3. {
4. get
5. {
6. return strFirstName;
7. }
8. set
9. {
10. SetPropertyValue<string>(ref strFirstName, value);
11. }
12. }
Now when you assign any property value, the set routine will call the SetPropertyValue() method and pass its name to the SetPropertyValue() method. Inside the SetPropertyValue() method you use this name (propName parameter) without hard-coding the actual property name.
Summary
.NET Framework 4.5 introduces Caller Info Attributes that can be used to obtain information about the caller of a method. Three attributes, viz. [CallerMemberName], [CallerFilePath] and [CallerLineNumber] supply caller name, its source file and the line number at which the call was made. You can use caller info attributes for tracing, debugging, logging or diagnostic purposes.