Get name from variable in calling method - creatin

2019-06-10 08:08发布

问题:

I'm looking for a way to get hold of the name of a variable that was passed into an extensionmethod. I want to have the name of the parameter in the calling variable. Sounds strange, let me explain.

assume this piece of testing code

    private static void TestingMethod(string firstParam, int secondParam, bool thirdParam)
    {
        try
        {
            throw new InvalidOperationException("This named optional params stuff, it's just not working boss");
        }
        catch (Exception ex)
        {
            GenericLog_Exception(ex, "it's on fire, please help");
        }
    }

On the last line you can see the exception being logged. I want to be able to provide optional parameter support. So the developers can add parameter information when needed.

I've seen many posts about this on stackoverflow, lot's of different approaches. Main thing is; it can't be done fully generically.

Some code for explanation:

    static string GetCallingMethodSignature()
    {
        StackTrace stackTrace = new StackTrace();

        // Get calling method name
        var callingMethod = stackTrace.GetFrame(1).GetMethod();
        var callingMethod_Name = callingMethod.Name;

        // get calling method params
        string retVal = string.Empty;

        var callingMethod_Parameters = callingMethod.GetParameters();

        retVal = callingMethod_Name + "(";
        foreach (var param in callingMethod_Parameters)
        {
            retVal += param.Name + ": " + param.ToString() + ",";
        }
        retVal.Remove(retVal.Length - 1, 1);
        retVal += ")";

        return retVal;
    }

Now, this testing code is getting the calling method name and it's parameters. That is, the names of the parameters. But not their values. The param.tostring() part only returns the type name. Not the value. I've been reading on this and it seems this can't be done via reflection.

So then I went for another approach, why not provide the parameters the developer finds suitable for logging. You don't need all of them most of the time anyway.

private static string GenericLog_Exception(Exception exceptionData, string extraInformation, params KeyValuePair<string, object>[] parameters)

So, this being a new testmethod, i'm providing the parameters of choice into the exception logging method. But if you want to make this work, it's one hell of a job everytime to make this call.

    private static void TestingMethod(string firstParam, int secondParam, bool thirdParam)
    {
        try
        {
            throw new InvalidOperationException("This named optional params stuff, it's just not working boss");
        }
        catch (Exception ex)
        {
            GenericLog_Exception(ex, "it's on fire, please help", new KeyValuePair<string, object>[]{
                new KeyValuePair<string, object>("firstParam", firstParam),
                new KeyValuePair<string, object>("secondParam", secondParam),
                new KeyValuePair<string, object>("thirdParam", thirdParam)
            });
        }
    }

Now this is working. But as said, i find the bottom part to cumbersome. I was thinking along the lines of an extensionmethod, so I can shorten the creation of each kvp.

internal static class ExtensionMethodsForTesting
{
    internal static KeyValuePair<string, object> AsKeyValuePair(this string parameter)
    {
        var name = nameof(parameter);
        return new KeyValuePair<string, object>(name, parameter);
    }
}

And this would then be used as

GenericLog_Exception(ex, "it's on fire, please help", new KeyValuePair<string, object>[] { firstParam.AsKeyValuePair() });

This confronts me with the same issue I had before; the nameof(parameter), ofcourse, returns "parameter". I would also have to make a couple of extension methods for each type. Or check for the type in the extension method to make sure i get the correct value.

So, in short: how can i get the name of this variable that invokes the extension method?

回答1:

You could do the following "hack". Change the signature of your method to the following:

private static string GenericLog_Exception(
        Exception exceptionData, 
        string extraInformation, 
        params Expression<Func<object>>[] parameters)

And now your call site would look a little bit cleaner:

GenericLog_Exception(ex, 
                     "it's on fire, please help",
                     () => firstParam,
                     () => secondParam,
                     () => thirdParam);

And you can extract parameter info from the expressions the following way (using C# tuple support for convenience):

private static (object Value, string ParamName) GetParameterInfo
    (Expression<Func<object>> expr)
{
    //First try to get a member expression directly.
    //If it fails we know there is a type conversion: parameter is not an object 
    //(or we have an invalid lambda that will make us crash)
    //Get the "to object" conversion unary expression operand and then 
    //get the member expression of that and we're set.
    var m = (expr.Body as MemberExpression) ??
            (expr.Body as UnaryExpression).Operand as MemberExpression;

    return (expr.Compile().Invoke(), m.Member.Name);
}