C# – Get argument names automatically

You can use the CallerArgumentExpression attribute to automatically get the name of an argument being passed into a method:

using System.Runtime.CompilerServices;

void Log(object objToLog, [CallerArgumentExpression("objToLog")] string argumentName = null)
{
    Console.WriteLine($"name={argumentName} value={objToLog}");
}
Code language: C# (cs)

Note: CallerArgumentExpression was added in .NET 6.

Here’s an example to show what CallerArgumentExpression does:

var employee = new Employee()
{
    FirstName = "Bob"
};
Log(employee.FirstName);
Code language: C# (cs)

Calling this method outputs the following:

name=employee.FirstName value=BobCode language: plaintext (plaintext)

You use the CallerArgumentExpression attribute with a default string parameter (i.e. string argumentName = null). Give it the name of the parameter you’re targeting – CallerArgumentExpression(“objToLog”) means I want to capture the argument name for the objToLog parameter.

When the caller doesn’t pass in a value for string argumentName, the compiler passes in the target parameter’s argument name. Hence, when you call Log(employee.FirstName), it’s really calling it like this Log(employee.FirstName, “employee.FirstName”).

Argument expression vs argument name

They use the terminology “argument expression” because you can pass anything in as an argument (literal values, objects, method calls). This is referred to as the argument expression. However, CallerArgumentExpression’s real world value is capturing argument names so that the caller doesn’t have to pass it in via a string literal or using nameof(). Therefore, it’s fine to refer to this as capturing the “argument name” if that’s what you’re using it for.

It passes in exactly what you typed when calling the method

CallerArgumentExpression is very useful for things like logging methods and exception throw helper methods. Because of this, you’ll mostly use this with object references and object properties. It will log the name of the object variable or a property name (with dot notation) you passed into the method.

However, you can pass in anything to a method – including literal values, method calls, anonymous types, etc… And CallerArgumentExpression captures exactly what you typed in. Here’s an example of calling it with literal values and null:

Log(1);
Log("hello world");
Log(null);
Log(10.50m);
Code language: C# (cs)

This outputs the following. Notice that the name is exactly what was typed in:

name=1 value=1
name="hello world" value=hello world
name=null value=
name=10.50m value=10.50Code language: plaintext (plaintext)

Note: For string literals, it escapes it. So the name of the string literal above is really \”hello world\”.

Here’s an example of passing in the output of a method call:

ArgumentNullException.ThrowIfNull(GetObj());
Code language: C# (cs)

Note: This is using the built-in ArgumentNullException.ThrowIfNull() helper that was added in .NET 6.

Here’s what this outputs:

 System.ArgumentNullException: Value cannot be null. (Parameter 'GetObj()')Code language: plaintext (plaintext)

It’s showing Parameter ‘GetObj()’ because it passes in the argument expression exactly as you typed it (i.e. GetObj()).

Override the argument name to bypass the CallerArgumentExpression behavior

This “captures exactly what you typed in” behavior can be a bit surprising and potentially undesirable in some scenarios, especially if CallerArgumentExpression is being used in a method you’re using but don’t have control over (such as ArgumentNullException.ThrowIfNull()).

You can override the argument name by explicitly passing in a value:

Log(employee.FirstName, "employeeName");
Code language: C# (cs)

This outputs the following, using the passed in “employeeName” value:

name=employeeName value=BobCode language: plaintext (plaintext)

Doesn’t work with params array

Params arrays are useful when you want to be able to accept any number of parameters.

void Process(params string[] args)
{
    //do stuff with the params
}

Process("1", "abc", "z");
Code language: C# (cs)

How about if you want to know the names of the arguments being passed in? CallerArgumentExpression doesn’t work with params arrays.

Params arrays must be the last thing in the parameter list. CallerArgumentExpression can only be added to a default parameter (i.e. string argumentName = null). These two constraints collide, preventing you from even attempting to use CallerArgumentExpression with a params array.

Instead, if you need a dynamic parameter list and want to know the argument names, you can use a dictionary instead of a params array. Here’s an example of initializing a dictionary with argument names mapped to values and passing it into the Process() method:

void Process(Dictionary<string, string> paramMap)
{
    //do stuff with the params
}


Process(new Dictionary<string, string>()
{
    [nameof(employee.FirstName)] = employee.FirstName,
    [nameof(employee.LastName)] = employee.LastName
});
Code language: C# (cs)

Alternatively, you could pass in an anonymous type and serialize to JSON when logging.