Chris Wright
Chris Wright

Reputation: 301

Obtaining original variable name from within an extension method

We are currently working on a logging solution and have implemented an extension method call 'Log'. When writing to the log file, we would ideally like to write the original variable name (rather than the variable name used in the extension method).

What we are currently having to do for this is:

public void DoSomeWork()
{
    String testString = "Hey look I'm a string!";
    testString.Log("testString value");
}

With the extention method:

public static String Log(this String valueToStore, String name)
{
    // The logging code which uses the 'name' parameter
}

The issue here is that it becomes difficult to read on longer lines of code and looks clustered. What would be ideal is this:

public void DoSomeWork()
{
    String testString = "Hey look I'm a string!";
    testString.Log();
}

With the extension method:

public static String Log(this String valueToStore)
{
    // The logging code which is able to retrieve the 
    // value 'testString' from the 'valueToStore' value
}

Is this at all possible by using Reflection? I'm aware of the nameofoption, but that only returns the string 'valueToStore' when used in the extension method.

Upvotes: 8

Views: 2897

Answers (4)

Ammarz
Ammarz

Reputation: 360

C# 10 has CallerArgumentExpressionAttribute that will do just that

public static void PrintNameAndValue(
    this object obj,
    [System.Runtime.CompilerServices.CallerArgumentExpression("obj")] string callerExp = ""
    )
{
    Console.WriteLine(callerExp + " = " + obj.ToString());
}

It'll capture the entire expression passed:

public void TestPrintNameAndValue()
{
    string mystring = "test";
    int myint = 5;

    mystring.PrintNameAndValue();             // mystring = test
    myint.PrintNameAndValue();                // myint = 5
    (myint + 10).PrintNameAndValue();         // myint + 10 = 15
    mystring.ToUpper().PrintNameAndValue();   // mystring.ToUpper() = TEST
}

Upvotes: 5

Gerino
Gerino

Reputation: 1983

Well, short answer is no. The variable names are not guaranteed to persist after compilation in unchanged form. That information would have to be somehow persisted (for example by the use of nameof()). Also, the variable name might not exist ("test".GetVarName()).

The long answer is: yes, possibly, but it's one of the most ridiculous things I've created in my life:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
            var myVarName = "test";
            myVarName.Test();
            Console.ReadKey();
        }
    }

    static class Extensions
    {
        public static void Test(
            this string str,
            [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
            [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
            [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
        )
        {
            var relevantLine = File.ReadAllLines(sourceFilePath)[sourceLineNumber-1];
            var currMethodName = MethodInfo.GetCurrentMethod().Name;
            var callIndex = relevantLine.IndexOf(currMethodName + "()");
            var sb = new Stack<char>();

            for (var i = callIndex - 2; i >= 0; --i)
            {
                if (Char.IsLetterOrDigit(relevantLine[i]))
                {
                    sb.Push(relevantLine[i]);
                }
            }

            Console.WriteLine(new String(sb.ToArray()));
        }
    }
}

Upvotes: 4

haim770
haim770

Reputation: 49135

You can use an Expression to achieve that, but performance-wise it may not be the best option:

public static void Log<T>(Expression<Func<T>> expr)
{
    var memberExpr = expr.Body as MemberExpression;

    if (memberExpr == null)
        return;

    var varName = memberExpr.Member.Name;
    var varData = expr.Compile()();

    // actual logging
    ...
}

Usage:

var test = "Foo";
Log(() => test);

Alternatively, if you're using C# 6.0, it can get a bit better using the nameof operator:

test.Log(nameof(test));

A better solution would be one that is leveraging the compiler abilities (specifically, the "Roslyn" compiler) and provide the member name on compile time.

Upvotes: 2

David
David

Reputation: 43

Not really an answer, more of a pointer, but you could try doing something with your application that you're using(e.g. visual studio) instead of doing it in code. What I mean is make it rewrite everything that looks like [variable].Log(); to [variable].Log([variable])

I am pretty sure that there has to be some weird macro or plugin which does this for you before compiling.

Upvotes: 1

Related Questions