Daily Knowledge Drop
As part of the C# System.Runtime
assembly, there are a number of Attributes
which can be used to get information about the caller of a method
.
The pattern for usage is the same for all attributes. To extract the metadata when a method is called, the following needs to be done:
- A
new parameter
needs to be added to the method (the type of the parameter depends on the attribute, see examples below) - The new parameter must have a
default value
- The new parameter must be
decorated
with the relevantAttribute
Attributes
Lets look at a few examples. All examples built using .NET6 minimal console application.
Line Number
To get the caller line number, the CallerLineNumber
attribute is used.
LogMessageWithLineNumber("Message 1");
static void LogMessageWithLineNumber(string message, [CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine($"LineNumber={lineNumber}: {message}");
}
In the method signiture above, we can see a decorated parameter, with a default value has been added. This field will automatically be populated with the line number of the calling method
The output is as follows:
LineNumber=4: Message 1
File Path
To get the file path of the calling method, the CallerFilePath
attribute is used.
LogMessageWithPath("Message 2");
static void LogMessageWithPath(string message, [CallerFilePath] string filepath = "")
{
Console.WriteLine($"Path={filepath}: {message}");
}
The output is as follows:
Path=C:\Development\Projects\Blog\CallerMetadata\CallerMetadata\Program.cs: Message 2
Method Name
To get the calling method name, the CallerMemberName
attribute is used.
LogMessageWithPath("Message 2");
static void LogMessageWithMethod(string message, [CallerMemberName] string memberName = "")
{
Console.WriteLine($"Method={memberName}: {message}");
}
The output is as follows:
Method=<Main>$: Message 3
As the example is using the .NET6 Console minimal project, there is no explicitly defined main method - hence the method reflecting as <Main>$
Lets look at another example.
new ClassCall().DoubleHop();
class ClassCall
{
public void DoubleHop()
{
LogMessageWithMethod("Message from class");
}
static void LogMessageWithMethod(string message,
[CallerMemberName] string memberName = "")
{
Console.WriteLine($"Method={memberName}: {message}");
}
}
In this example, the source of the call to LogMessageWithMethod is the DoubleHop method, as such the output is as follows:
Method=DoubleHop: Message from class
Expression
The last attribute is the most interesting and the most useful. The CallerArgumentExpression
attribute is used to get the expression used as the argument to the method.
In the following example, the same string message "Message 4" will be logged, however the string will be generated differently each time the method is invoked.
// Just send a string
LogMessageWithExpression("Message 4");
// Add two string together with the + operator
LogMessageWithExpression("Message" + " 4");
// Concatenate two string using Concat method
LogMessageWithExpression(String.Concat("Message", " 4"));
static void LogMessageWithExpression(string value,
[CallerArgumentExpression("value")] string expression = "")
{
Console.WriteLine($"{{{value}}} was generated by {{{expression}}}");
}
In this case, the CallerArgumentExpression
attribute takes a parameter itself, and that is the name of the parameter for which is should get the caller argument expression
.
The output is as follows:
{Message 4} was generated by {"Message 4"}
{Message 4} was generated by {"Message" + " 4"}
{Message 4} was generated by {String.Concat("Message", " 4")}
We can see from the output, that the expression parameter gets populated with the code used to generate the parameter value
(the expression for the parameter argument).
This can be invaluable when logging, as not only can a piece of information be logged, but also which piece of code is generating the information.
Overriding parameter values
In all of the above examples, as mentioned, the parameter decorated with the relevant attribute needs to have a default value. So what happens if a value is explicitly supplied for the parameter
?
As one might expect, instead of using the default and allowing the attribute to populate the parameter, the explicitly supplied value is used
.
Here a value is explicitly supplied for the memberName parameter:
LogMessageWithMethod("Message 5", "OwnMethodName");
static void LogMessageWithMethod(string message,
[CallerMemberName] string memberName = "")
{
Console.WriteLine($"Method={memberName}: {message}");
}
The output, as expected:
Method=OwnMethodName: Message 5
Notes
While simple to implement, using these features can be very intrusive, with the need to add additional parameters to relevant methods. However if required, these tools can provide invaluable information about the calling location.
References
Get C# Metadata From a Call Site
Daily Drop 09: 11-02-2022
At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.
The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.