4

Is there any way to get the original name of the variable/argument, as it was named, in the caller method? In a way, it's like [CallerMemberName] attribute, but for the variable/argument.

UPDATE 2018.02: This question is similar to these ones: 1, 2 and 3, but there is still no valid, standardized solution at the begin of 2018. The proposed solution: using Expression<Func<T>> can have non-documented effects and is quite CPU-intensive, since converting a code into an expression to get the original name of a variable only is not a simple action. It is like creating a 1М-sized empty array to store single integer number instead of simple integer – very not effective («з гармати по горобцям»).

Short example:

int ComplexMathMethod(int startPos, int endPos,
    int backgroundStartPos, int backgroundEndStart, 
    int squareStartPos, // …and other positions… )
{
    CheckPosition(startPos);
    CheckPosition(endPos);
    CheckPosition(backgroundStartPos);
    CheckPosition(backgroundEndStart);
    CheckPosition(squareStartPos);
    // …and so on…  
}

void CheckPosition(int position)
{
    bool isValidPosition = // complex check;
    if (!isValidPosition)
    {
        throw new ArgumentException(nameof(position));

        // --->>> It will be extremely convenient to automatically replace "nameof(position)" with original 
        // name of variable/argument from caller method, like: "startPos", "endPos", "backgroundStartPos", etc.

        // Additional string argument with original variable name is not a good solution too.
        // Because, I have similar "check" method that checks 4 connected position’s integers at once.
        // Therefore, 4 input ints with additional +4 string arguments will make a mess.
    }
}

Long example:

void ShowMessage(string messageStr)
{

    // Problem: get original variable/argument name, as it was called, in the caller method.
    string callerMethodArgName;

    // If method was called from "Main" method, than callerMethodArgName should equal to "finishMsg".
    // If method was called from "DoWork" method, than callerMethodArgName should equal to "taskName".
    // If method was called from "StartProgram" method, than callerMethodArgName should equal to "startingMsg".    

    // …missing logic, reflection code, etc.

    // The next line is just a stub.
    callerMethodArgName = nameof(messageStr);

    Console.WriteLine($"Variable name is \"{callerMethodArgName}\"; value is \"{messageStr}\".");

}

void Main()
{
    StartProgram();
    DoWork("Programming…");

    // Example 1.
    string finishMsg = "Finish!";
    ShowMessage(finishMsg);
}

// Example, caller method 2.
void StartProgram()
{
    string startingMsg = "Starting program";
    ShowMessage(startingMsg);
}

// Example, caller method 3.
void DoWork(string taskName)
{
    ShowMessage(taskName);
}

Current program output:

// Variable name is "messageStr"; value is "Starting program".
// Variable name is "messageStr"; value is "Programming…".
// Variable name is "messageStr"; value is "Finish!".

Required/desired output:

// Variable name is "startingMsg"; value is "Starting program".
// Variable name is "taskName"; value is "Programming…".
// Variable name is "finishMsg"; value is "Finish!".

I am not sure if this is possible at all. I spent several hours playing with reflection, but unfortunately, it was fruitless. This would be extremely convenient while debugging in real project.

Oleg Zarevennyi
  • 2,753
  • 1
  • 21
  • 21
  • 1
    Check out the StackTrace object @ https://stackoverflow.com/questions/171970/how-can-i-find-the-method-that-called-the-current-method – Frode Feb 16 '18 at 20:05
  • Int is a value Type therefore cant get a reference to it. They are passed by value. In your Case i would Just Pass a String to the Checkposition method – Mightee Feb 16 '18 at 20:07
  • 1
    Ok. read it again. You wanted the name of the original variable. Maybe StackTrace would help. Not sure. Why not use an enumerator parameter? Otherwise your code would be hard for others to understand/maintain – Frode Feb 16 '18 at 20:07
  • @Frode Thank, but I had spent several hours playing with StackTrace (reflection), but unfortunately, it was fruitless. It is easy to get method name or all argument's names of the merhod only. – Oleg Zarevennyi Feb 16 '18 at 20:09
  • @Mightee Additional string argument with original variable name is simple, but not a good solution. Because, I have similar "check" method that checks 4 connected position’s integers at once. Therefore, 4 input ints with additional +4 string arguments will make a mess. – Oleg Zarevennyi Feb 16 '18 at 20:11
  • @Frode What do you mean? May you, please, show an example with enumerator? – Oleg Zarevennyi Feb 16 '18 at 20:12
  • Then Just Log the Check before you call the method. Something Like `Debug.Log(" checking Position for x");` and then in the exception you Log that Error – Mightee Feb 16 '18 at 20:20
  • 1
    Duplicate of [https://stackoverflow.com/q/9801624/767890](https://stackoverflow.com/q/9801624/767890) – InBetween Feb 16 '18 at 20:26
  • @InBetween The answer to that qustion relies on a non-standardized behaviour of the Microsoft C# compiler, and might break under other compilers or future versions and it also creates a new wrapper-function each time. Please check - https://stackoverflow.com/a/11071271/1998097 – Oleg Zarevennyi Feb 16 '18 at 20:51
  • 1
    @OlegZarevennyi It uses `Expression>` exactly the same as Aleks answer. What is it you think can break in future versions? I'm honestly interested to know. – InBetween Feb 16 '18 at 20:53

2 Answers2

4

You can use Linq.Expressions for this. First you need to change a signature of CheckPosition like this:

void CheckPosition(Expression<Func<int>> positionExpression)

After that you can call it like this:

CheckPosition(() => startPos);

Full code:

int ComplexMathMethod(int startPos, int endPos,
    int backgroundStartPos, int backgroundEndStart,
    int squareStartPos) // …and other positions… )
{
    CheckPosition(() => startPos);
    CheckPosition(() => endPos);
    CheckPosition(() => backgroundStartPos);
    CheckPosition(() => backgroundEndStart);
    CheckPosition(() => squareStartPos);
    // …and so on…  

    return 0;
}

void CheckPosition(Expression<Func<int>> positionExpression)
{
    var name = ((MemberExpression) positionExpression.Body).Member.Name;
    var value = positionExpression.Compile().Invoke();

    Console.WriteLine($"Name = {name}, value ={value}");
}
Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37
  • Thank you for an example, this will work. Nevertheless, the disadvantage of this method is that new wrapper-function object: “Func” will be created on each check. It would be nice to find solution that will work without a wrapper, if it possible. – Oleg Zarevennyi Feb 16 '18 at 20:46
  • Also **IMPORTANT**: "This answer relies on a non-standardized behaviour of the Microsoft C# compiler, and might break under other compilers or future versions.", thanks to @Douglas – Oleg Zarevennyi Feb 16 '18 at 20:50
3

You can do this with the CallerArgumentExpression attribute from C# 10 and up.

The CallerArgumentExpression attribute allows accessing the name of an argument that was passed as a parameter to a method.

Here's an example based on your original code:

void ShowMessage(
    string messageStr, 
    [CallerArgumentExpression("messageStr")] string callerMethodArgName= "")
{
    // callerMethodArgName contains the name of the argument that was passed in to the messageStr parameter
    Console.WriteLine($"Variable name is \"{callerMethodArgName}\"; value is \"{messageStr}\".");
}
dybzon
  • 1,236
  • 2
  • 15
  • 21