【问题标题】:Finding the variable name passed to a function查找传递给函数的变量名
【发布时间】:2010-09-09 11:37:05
【问题描述】:

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

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

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

当我将变量WhatIsMyName 传递给ExampleFunction 时,我希望能够获得原始变量名称的字符串。也许是这样的:

Variable.OriginalName.ToString()

有什么办法吗?

【问题讨论】:

  • 这是一个反复出现的主题,让我抓狂。一个智力问题,将被用来做某事,而不是答案..人们想知道你为什么要这样做..那里有足够的逻辑来回答这个问题......
  • Johnny5 的答案应该是被接受的,请更改它@GateKiller
  • @JonLimjap storeValue(my_variable) 而不是 storeValue(my_variable,"my_variable")
  • 实际上@reggaeguitar,这不太正确。您将在示例中看到 GateKiller 希望能够将变量传递给方法,然后从该方法中获取变量的名称。使用 nameof 并不能解决问题,因为现在您必须将名称传递给要使用它的任何方法。问题的重点是能够确定名称,而无需将其传递到您需要的任何地方。 Johnny5 的信息很有用,但不是正确答案。
  • @WiredWiz 你是对的,我的立场是正确的。

标签: c# asp.net .net


【解决方案1】:

您想要的无法直接实现,但您可以使用 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# 编译器中,但不能保证这在未来的版本中不会停止工作。

【讨论】:

  • 有什么方法可以用属性而不是局部变量来做到这一点?谢谢。
  • 对于Expression,一个局部变量实际上一个属性(.Member.Name——这是编译器创建的闭包实现的直接结果lambda 表达式),所以上面的代码也应该适用于属性。
  • 此答案依赖于 Microsoft C# 编译器的非标准化行为,并且可能会在其他编译器或未来版本下中断。参考我的questionanswer的话题。
  • @Douglas 谢谢,很高兴知道。对于它的价值,Eric Lippert 对此的咆哮似乎有点不成熟。其他语言确实对此进行了标准化,他的整个咆哮似乎是基于这样一个事实,即指定这种行为是 baaaad,这似乎是完全错误的,或者至少是完全不明显的。相反:这是该功能的自然实现,它高效、安全,没有先验的理由不对其进行标准化。
  • @Soko 发布了一个重复的问题,该问题收到了相同的答案并附有更多详细信息:stackoverflow.com/a/37640136/228738
【解决方案2】:

这不是完全可能的,你想要的方式。 C# 6.0 他们引入了 nameof 运算符,这将有助于改进和简化代码。运算符的名称解析传入的变量的名称。

您的案例的用法如下所示:

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 表达式是一个常量。在所有情况下,nameof(...) 在编译时被评估以产生一个字符串。它的参数在运行时不会被评估,并且被认为是不可访问的代码(但是它不会发出“不可访问代码”警告)。

更多信息可以找到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;
}

【讨论】:

    【解决方案3】:
    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

    【讨论】:

    • 链接已损坏,但解决方案看起来非常简洁。或许您可以在这里添加一些细节?
    • var variableName = "value";结果 = (new { variableName }).GetType().GetProperties()[0].Name; new { variableName } 用 variableName = "value" 属性创建匿名类,这样 .GetProperties()[0].Name 就得到这个属性的名字
    • 不包装参数,你可以做new { item }.GetType().GetProperties()[0].Name
    【解决方案4】:

    三种方式:

    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 又慢了。

    【讨论】:

    • 谢谢,我正在使用 2) .. 如果我的匿名函数中有多个参数怎么办? :)
    【解决方案5】:

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

    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;
            }
        }
    

    【讨论】:

    • 干得好。它应该可以工作,但它应该对性能有很大的影响,因为它意味着读取源文件。我想知道是否有更有效的方法来完成同样的工作?
    • 真的太可怕了,出色的工作!如果nameof 允许您查找堆栈的一层,那就太好了,因此nameof(x,1) 会为您提供调用者作用域中作为参数x 传递给当前方法的表达式的名称。
    【解决方案6】:

    我知道这篇文章真的很老了,但既然现在 .Net 6 中有一种方法,我想我会分享给其他人知道。

    您现在可以使用 CallerArgumentExpressionAttribute,如图所示

        /// <summary>
        /// Will throw argument exception if string IsNullOrEmpty returns true
        /// </summary>
        /// <param name="str"></param>
        /// <param name="str_name"></param>
        /// <exception cref="ArgumentException"></exception>
        public static void ValidateNotNullorEmpty(this string str,
            [CallerArgumentExpression("str")]string str_name=null)
        {       
            if (string.IsNullOrEmpty(str))
            {
                throw new ArgumentException($"'{str_name}' cannot be null or empty.", str_name);
            }
        }
    

    现在调用:

    param.ValidateNotNullorEmpty();
    

    将抛出错误:“参数不能为空或为空。”

    而不是“str不能为null或为空”

    【讨论】:

      【解决方案7】:

      这样做非常有用,可以创建良好的异常消息,使人们能够更好地查明错误。行号有帮助,但您可能无法在 prod 中获得它们,并且当您获得它们时,如果代码中有大语句,您通常只会获得整个语句的第一行。

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

      创建类似 Verify.NotNull(myvar, nameof(myvar)) 的代码是迄今为止我发现的最好的解决方法,但如果不需要添加额外的参数,那就太好了。

      【讨论】:

      • 这正是我们在项目中所做的。我们使用异常来标记失败的方法前提条件。这些需要包含有用的参数名称。我们现在到处都有Verify.SomeCondition(arg, nameof(arg))
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

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

        【讨论】:

          【解决方案10】:

          试试这个实用程序类,

          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;
              }
          

          【讨论】:

            【解决方案11】:

            这样做

            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");
                }        
            }
            

            代码经过测试,是我能想到的最优雅的。

            【讨论】:

            • 我个人更喜欢手动命名,因为使用表达式选项意味着变量引用本身,因此可以避免智能感知 IDE 助手突出显示对已使用变量的引用。有点费时,但通常更具可读性。
            • 但是,如果您使用的是真正具有描述性的变量名,那么前者会非常有用。
            • 如果 Named() 2 个变量引用同一个实例,这将不起作用。见this例子
            • 没错,这是设计使然,唯一的对象应该只有一个名称。如果实例已经命名,也许应该抛出异常。可以修改代码来克服这个问题,但是获取对象的名称必须返回一个列表。我认为按照良好的设计原则,目前的代码可以满足大多数代码需求。我真的不记得我上次创建同一实例的对象是什么时候了。感谢您向我介绍 .NET Fiddle,非常好。
            【解决方案12】:

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

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

            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 类称为错误。我希望我的感冒能根据我发送的变量名找出要使用的类。

            再次感谢所有回复。

            【讨论】:

              【解决方案13】:

              继续Caller* 属性系列(即CallerMemberNameCallerFilePathCallerLineNumber),CallerArgumentExpressionAttribute 从 C# Next 开始可用(更多信息 here)。

              以下示例的灵感来自 Paul Mcilreavy 的 The CallerArgumentExpression Attribute in C# 8.0

              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");
                  }        
              }
              

              【讨论】:

                【解决方案14】:

                感谢visual studio 2022,你可以使用这个

                功能

                       public void showname(dynamic obj) {
                            obj.GetType().GetProperties().ToList().ForEach(state => {
                                NameAndValue($"{state.Name}:{state.GetValue(obj, null).ToString()}");
                            });
                        }
                

                使用

                   var myname = "dddd";
                   showname(new { myname });
                

                【讨论】:

                  【解决方案15】:

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

                  做到这一点的唯一方法是通过反射和堆栈遍历。您必须获得一个堆栈帧,在调用函数中找出您从中调用的位置,然后使用 CodeDOM 尝试找到树的正确部分以查看表达式是什么。

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

                  【讨论】:

                  • 这是在 2008 年回答的,所以我不得不投票以摆脱负面投票
                  【解决方案16】:

                  没有。对您的字符串变量的引用被传递给函数——不包含任何关于它的固有元数据。即使是反射也不能让你走出困境——从单一引用类型向后工作并不能为你提供足够的信息来做你需要做的事情。

                  最好回到这个绘图板上!

                  rp

                  【讨论】:

                    【解决方案17】:

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

                    【讨论】:

                      【解决方案18】:

                      嗯,看了一下。 当然你不能使用任何类型信息。 此外,局部变量的名称在运行时不可用 因为它们的名称没有编译到程序集的元数据中。

                      【讨论】:

                        【解决方案19】:

                        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);
                        

                        【讨论】:

                          【解决方案20】:

                          获取它的一种方法是读取代码文件并用逗号和括号将其分割...

                          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();
                          

                          【讨论】:

                            【解决方案21】:

                            如果我理解正确,您希望字符串“WhatIsMyName”出现在 Hello 字符串中。

                            string Hello = ExampleFunction(WhatIsMyName);
                            

                            如果用例是它增加了 ExampleFunction 的可重用性并且 Hello 应包含类似“Hello, Peter (from WhatIsMyName)”的内容,那么我认为解决方案是将 ExampleFunction 扩展为接受:

                            字符串 Hello = ExampleFunction(WhatIsMyName,nameof(WhatIsMyName));

                            以便名称作为单独的字符串传递。是的,这与您所要求的不完全一样,您必须输入两次。但它是重构安全的,可读的,不使用调试接口,而且出错的可能性很小,因为它们一起出现在消费代码中。

                            string Hello1 = ExampleFunction(WhatIsMyName,nameof(WhatIsMyName));
                            string Hello2 = ExampleFunction(SomebodyElse,nameof(SomebodyElse));
                            string Hello3 = ExampleFunction(HerName,nameof(HerName));
                            

                            【讨论】:

                              【解决方案22】:

                              不。我不这么认为。

                              您使用的变量名是为了您的方便和可读性。编译器不需要它,如果我没记错的话就把它扔掉。

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

                              【讨论】:

                              • 这个原始问题的较新副本可能有一个可能的答案是:stackoverflow.com/questions/9801624/…
                              • @alfa,您链接到的答案给出了参数名称,而不是本问题所要求的传递给函数的参数名称。
                              • 实际参数是计算表达式,而不是变量。
                              • 用于回答和提供可能的解决方案。
                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 2017-07-21
                              • 1970-01-01
                              • 2011-02-26
                              • 2013-10-08
                              • 2012-04-13
                              相关资源
                              最近更新 更多