查找传递给函数的变量名称

时间:2008-09-16 13:24:56

标签: c#

让我用下面的例子来解释我的问题:

public string ExampleFunction(string Variable) {
    return something;
}

string WhatIsMyName = "Hello World"';
string Hello = ExampleFunction(WhatIsMyName);

当我将变量“WhatIsMyName”传递给示例函数时,我希望能够获取原始变量名称的字符串。也许是这样的事情:

Variable.OriginalName.ToString()

有没有办法做到这一点?

19 个答案:

答案 0 :(得分:54)

您想要的不是直接的,但您可以在C#3.0中使用表达式:

public void ExampleFunction(Expression<Func<string, string>> f) {
    Console.WriteLine((f.Body as MemberExpression).Member.Name);
}

ExampleFunction(x => WhatIsMyName);

请注意,这取决于未指定的行为,虽然它在Microsoft的当前C#和VB编译器中工作,但在Mono的C#编译器中,但无法保证在将来的版本中不会停止工作。

答案 1 :(得分:32)

我知道这是一个老问题但是在C#6.0中他们引入了应该解决这个问题的运营商名称。运算符的名称解析传递给它的变量的名称。

您案件的用法如下:

public string ExampleFunction(string variableName) {
      //Construct your log statement using c# 6.0 string interpolation
       return $"Error occurred in {variableName}";
}

string WhatIsMyName = "Hello World"';
string Hello = ExampleFunction(nameof(WhatIsMyName));

一个主要好处是它在编译时完成,

  

表达式的名称是常量。在所有情况下,在编译时评估nameof(...)以生成字符串。它的参数在运行时不会被评估,并且被认为是无法访问的代码(但它不会发出&#34;无法访问的代码&#34;警告)。

可以找到更多信息here

旧版C 3.0及以上
建立Nawfals答案

GetParameterName2(new { variable });

//Hack to assure compiler warning is generated specifying this method calling conventions
[Obsolete("Note you must use a single parametered AnonymousType When Calling this method")]
public static string GetParameterName<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    return typeof(T).GetProperties()[0].Name;
}

答案 2 :(得分:17)

static void Main(string[] args)
{
  Console.WriteLine("Name is '{0}'", GetName(new {args}));
  Console.ReadLine();
}

static string GetName<T>(T item) where T : class
{
  var properties = typeof(T).GetProperties();
  Enforce.That(properties.Length == 1);
  return properties[0].Name;
}

更多详情请见this blog post

答案 3 :(得分:12)

三种方式:

1)根本没有反思的东西:

GetParameterName1(new { variable });

public static string GetParameterName1<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    return item.ToString().TrimStart('{').TrimEnd('}').Split('=')[0].Trim();
}

2)使用反射,但这比其他两个快。

GetParameterName2(new { variable });

public static string GetParameterName2<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    return typeof(T).GetProperties()[0].Name;
}

3)最慢的,不要使用。

GetParameterName3(() => variable);

public static string GetParameterName3<T>(Expression<Func<T>> expr)
{
    if (expr == null)
        return string.Empty;

    return ((MemberExpression)expr.Body).Member.Name;
}

要获取组合参数名称和值,可以扩展这些方法。当然,如果你将参数作为另一个参数单独传递,那么它很容易获得值,但这是不优雅的。代替:

1)

public static string GetParameterInfo1<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    var param = item.ToString().TrimStart('{').TrimEnd('}').Split('=');
    return "Parameter: '" + param[0].Trim() +
           "' = " + param[1].Trim();
}

2)

public static string GetParameterInfo2<T>(T item) where T : class
{
    if (item == null)
        return string.Empty;

    var param = typeof(T).GetProperties()[0];
    return "Parameter: '" + param.Name +
           "' = " + param.GetValue(item, null);
}

3)

public static string GetParameterInfo3<T>(Expression<Func<T>> expr)
{
    if (expr == null)
        return string.Empty;

    var param = (MemberExpression)expr.Body;
    return "Parameter: '" + param.Member.Name +
           "' = " + ((FieldInfo)param.Member).GetValue(((ConstantExpression)param.Expression).Value);
}

1和2现在速度相当,3再次缓慢。

答案 4 :(得分:4)

不,但每当你发现自己做了这样极其复杂的事情时,你可能想重新考虑一下你的解决方案。请记住,代码应该比编写代码更容易阅读。

答案 5 :(得分:4)

是的!有可能。我一直在寻找一个解决方案很长一段时间,并最终想出一个解决它的黑客(它有点讨厌)。我不建议将此作为程序的一部分使用,我只认为它在调试模式下工作。对我来说这没关系,因为我只在控制台类中使用它作为调试工具,所以我可以这样做:

int testVar = 1;
bool testBoolVar = True;
myConsole.Writeline(testVar);
myConsole.Writeline(testBoolVar);

控制台的输出是:

testVar: 1
testBoolVar: True

这是我用来做的功能(不包括我的控制台类的包装代码。

    public Dictionary<string, string> nameOfAlreadyAcessed = new Dictionary<string, string>();
    public string nameOf(object obj, int level = 1)
    {
        StackFrame stackFrame = new StackTrace(true).GetFrame(level);
        string fileName = stackFrame.GetFileName();
        int lineNumber = stackFrame.GetFileLineNumber();
        string uniqueId = fileName + lineNumber;
        if (nameOfAlreadyAcessed.ContainsKey(uniqueId))
            return nameOfAlreadyAcessed[uniqueId];
        else
        {
            System.IO.StreamReader file = new System.IO.StreamReader(fileName);
            for (int i = 0; i < lineNumber - 1; i++)
                file.ReadLine();
            string varName = file.ReadLine().Split(new char[] { '(', ')' })[1];
            nameOfAlreadyAcessed.Add(uniqueId, varName);
            return varName;
        }
    }

答案 6 :(得分:2)

System.Environment.StackTrace将为您提供一个包含当前调用堆栈的字符串。您可以解析它以获取信息,其中包括每个调用的变量名称。

答案 7 :(得分:2)

试试这个Utility类,

public static class Utility
{
    public static Tuple<string, TSource> GetNameAndValue<TSource>(Expression<Func<TSource>> sourceExpression)
    {
        Tuple<String, TSource> result = null;
        Type type = typeof (TSource);
        Func<MemberExpression, Tuple<String, TSource>> process = delegate(MemberExpression memberExpression)
                                                                    {
                                                                        ConstantExpression constantExpression = (ConstantExpression)memberExpression.Expression;
                                                                        var name = memberExpression.Member.Name;
                                                                        var value = ((FieldInfo)memberExpression.Member).GetValue(constantExpression.Value);
                                                                        return new Tuple<string, TSource>(name, (TSource) value);
                                                                    };

        Expression exception = sourceExpression.Body;
        if (exception is MemberExpression)
        {
            result = process((MemberExpression)sourceExpression.Body);
        }
        else if (exception is UnaryExpression)
        {
            UnaryExpression unaryExpression = (UnaryExpression)sourceExpression.Body;
            result = process((MemberExpression)unaryExpression.Operand);
        }
        else
        {
            throw new Exception("Expression type unknown.");
        }

        return result;
    }


}

和用户一样

    /*ToDo : Test Result*/
    static void Main(string[] args)
    {
        /*Test : primivit types*/
        long maxNumber = 123123;
        Tuple<string, long> longVariable = Utility.GetNameAndValue(() => maxNumber);
        string longVariableName = longVariable.Item1;
        long longVariableValue = longVariable.Item2;

        /*Test : user define types*/
        Person aPerson = new Person() { Id = "123", Name = "Roy" };
        Tuple<string, Person> personVariable = Utility.GetNameAndValue(() => aPerson);
        string personVariableName = personVariable.Item1;
        Person personVariableValue = personVariable.Item2;

        /*Test : anonymous types*/
        var ann = new { Id = "123", Name = "Roy" };
        var annVariable = Utility.GetNameAndValue(() => ann);
        string annVariableName = annVariable.Item1;
        var annVariableValue = annVariable.Item2;

        /*Test : Enum tyoes*/
        Active isActive = Active.Yes;
        Tuple<string, Active> isActiveVariable = Utility.GetNameAndValue(() => isActive);
        string isActiveVariableName = isActiveVariable.Item1;
        Active isActiveVariableValue = isActiveVariable.Item2;
    }

答案 8 :(得分:0)

继续使用 Caller* 属性系列(即 CallerMemberNameCallerFilePathCallerLineNumber),CallerArgumentExpressionAttribute 自 C# Next 起可用(更多信息 {{3 }}).

以下示例的灵感来自 Paul Mcilreavy 的 here

public static void ThrowIfNullOrWhitespace(this string self, 
             [CallerArgumentExpression("self")] string paramName = default)
{
    if (self is null)
    {
        throw new ArgumentNullException(paramName);
    }

    if (string.IsNullOrWhiteSpace(self))
    {
        throw new ArgumentOutOfRangeException(paramName, self, "Value cannot be whitespace");
    }        
}

答案 9 :(得分:0)

获取它的方法可以是读取代码文件并用逗号和括号将其拆分...

var trace = new StackTrace(true).GetFrame(1);
var line = File.ReadAllLines(trace.GetFileName())[trace.GetFileLineNumber()];
var argumentNames = line.Split(new[] { ",", "(", ")", ";" }, 
                               StringSplitOptions.TrimEntries)
                        .Where(x => x.Length > 0)
                        .Skip(1).ToList();

答案 10 :(得分:0)

这样做对于创建良好的异常消息(使人们能够更好地查明错误)非常有用。行号会有所帮助,但是您可能无法将它们放入产品中,当您获得它们时,如果代码中包含大语句,通常只会得到整个语句的第一行。

例如,如果在未设置的nullable上调用.Value,则会收到一条带有失败消息的异常,但是由于缺少此功能,因此您将看不到哪个属性为null。如果您在一条语句中执行两次此操作(例如,将参数设置为某种方法),则将看不到未设置什么可空值。

到目前为止,创建诸如Verify.NotNull(myvar,nameof(myvar))之类的代码是目前为止最好的解决方法,但是摆脱添加额外参数的需求将非常有用。

答案 11 :(得分:0)

这样做

var myVariable = 123;
myVariable.Named(() => myVariable);
var name = myVariable.Name();
// use name how you like

或手动命名代码

var myVariable = 123.Named("my variable");
var name = myVariable.Name();

使用此课程

public static class ObjectInstanceExtensions
{
    private static Dictionary<object, string> namedInstances = new Dictionary<object, string>();

    public static void Named<T>(this T instance, Expression<Func<T>> expressionContainingOnlyYourInstance)
    {
        var name = ((MemberExpression)expressionContainingOnlyYourInstance.Body).Member.Name;
        instance.Named(name);            
    }

    public static T Named<T>(this T instance, string named)
    {
        if (namedInstances.ContainsKey(instance)) namedInstances[instance] = named;
        else namedInstances.Add(instance, named);
        return instance;
    }        

    public static string Name<T>(this T instance)
    {
        if (namedInstances.ContainsKey(instance)) return namedInstances[instance];
        throw new NotImplementedException("object has not been named");
    }        
}

我可以提出经过代码测试和最优雅的测试。

答案 12 :(得分:0)

GateKiller,我的解决方法出了什么问题?您可以轻松地重写您的功能以使用它(我已经冒昧地改进了功能):

static string sMessages(Expression<Func<List<string>>> aMessages) {
    var messages = aMessages.Compile()();

    if (messages.Count == 0) {
        return "";
    }

    StringBuilder ret = new StringBuilder();
    string sType = ((MemberExpression)aMessages.Body).Member.Name;

    ret.AppendFormat("<p class=\"{0}\">", sType);
    foreach (string msg in messages) {
        ret.Append(msg);
        ret.Append("<br />");
    }
    ret.Append("</p>");
    return ret.ToString();
}

这样称呼:

var errors = new List<string>() { "Hi", "foo" };
var ret = sMessages(() => errors);

答案 13 :(得分:0)

感谢所有回复。我想我只需要继续我现在正在做的事情。

对于那些想知道我为什么问上述问题的人。我有以下功能:

string sMessages(ArrayList aMessages, String sType) {
    string sReturn = String.Empty;
    if (aMessages.Count > 0) {
        sReturn += "<p class=\"" + sType + "\">";
        for (int i = 0; i < aMessages.Count; i++) {
            sReturn += aMessages[i] + "<br />";
        }
        sReturn += "</p>";
    }
    return sReturn;
}

我发送一个错误消息数组和一个css类,然后将其作为网页的字符串返回。

每次调用此函数时,我都要定义sType。类似的东西:

output += sMessages(aErrors, "errors");

如您所见,我的变量称为aErrors,我的css类称为错误。我希望我的感冒可以根据我发送的变量名来找出要使用的类。

再次感谢所有回复。

答案 14 :(得分:0)

您可以使用反射来获取对象的所有属性,而不是循环遍历它,并获取属性的值(属性的名称)与传入的参数匹配。

答案 15 :(得分:0)

没有。对你的字符串变量的引用被传递给函数 - 它没有任何关于它的固有元数据。即使反射也不会让你走出困境 - 从单一参考类型向后工作并不能让你获得足够的信息来完成你需要做的事情。

最好回到这个的绘图板上!

RP

答案 16 :(得分:-1)

好好看一下。 当然你不能使用任何类型信息。 此外,本地变量的名称在运行时不可用 因为他们的名字没有编译成集会的元数据。

答案 17 :(得分:-1)

简短的回答是否定的......除非你真的很有动力。

这样做的唯一方法是通过反射和堆栈行走。你必须得到一个堆栈框架,在你调用的调用函数中找出行踪,然后使用CodeDOM尝试找到树的正确部分,看看表达式是什么。

例如,如果调用是ExampleFunction(“a”+“b”)?

,该怎么办?

答案 18 :(得分:-8)

**不。**我不这么认为。

您使用的变量名称是为了您的方便和可读性。编译器不需要它&amp;如果我没弄错的话就把它搞砸了。

如果有帮助,您可以定义一个名为NamedParameter的新类,其属性为Name和Param。然后,您将此对象作为参数传递。