查找传递给函数的变量名


71

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

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

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

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

Variable.OriginalName.ToString()

有什么办法吗?


41
这是一个反复出现的主题,使我发疯。人们会想知道为什么要这么做。这是一个理智的问题,将被用来做某事,而不是一个答案。。那里有足够的逻辑来回答这个问题……
Todd Painton 2014年

1
Johnny5的答案应该是被接受的答案,请更改它@GateKiller
reggaeguitar

2
@JonLimjapstoreValue(my_variable)代替storeValue(my_variable,"my_variable")
beppe9000 '17

2
实际上,@ reggaeguitar不太正确。您将在示例中看到GateKiller希望能够将变量传递给方法,然后从该方法中获取变量的名称。使用nameof不能解决问题,因为现在您必须将名称传递到要使用它的任何方法中。问题的关键是能够确定名称而不将其传递到需要的任何地方。Johnny5的信息很有用,但不是正确的答案。
WiredWiz

1
@WiredWiz你说得对,我已经纠正了。
reggaeguitar

Answers:


-8

不,我不这么认为。

您使用的变量名称是为了您的方便和可读性。编译器不需要它,如果我没有记错的话,只需将其剔除即可。

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


13
:这个原题的新副本可能是一个可能的答案 stackoverflow.com/questions/9801624/...
FIV

4
@alfa,您链接到的答案给出的是参数名称,而不是按照此问题的要求传递给函数的参数名称。
2013年

2
实际参数是评估表达式,而不是变量。
Tom Blodget 2014年

63

您想要的东西不可能直接实现,但是您可以在C#3.0中使用Expressions:

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

ExampleFunction(x => WhatIsMyName);

请注意,这依赖于未指定的行为,尽管它确实可以在Microsoft当前的C#和VB编译器以及Mono的C#编译器中工作,但不能保证它不会在将来的版本中停止工作。


有什么办法用属性而不是局部变量来做到这一点?谢谢。
pbz

3
就而言Expression,局部变量实际上一个属性(.Member.Name–这是编译器为实现lambda表达式而创建的闭包的直接结果),因此上述代码也应适用于属性。
康拉德·鲁道夫

1
该答案依赖于Microsoft C#编译器的非标准化行为,并且可能在其他编译器或将来的版本下崩溃。请参阅我的问题答案的话题。
道格拉斯

4
@道格拉斯,谢谢,很高兴知道。就其价值而言,埃里克·利珀特(Eric Lippert)对这件事的咆哮似乎还不成熟。其他语言确实对此进行了标准化,并且他的全部话语似乎是基于以下事实:指定此行为是baaaad,这似乎是错误的,或者至少是完全不明显的。相反:这是该功能的自然实现,它高效,安全,并且没有先验的理由不对其进行标准化。
康拉德·鲁道夫

1
@Soko发布了一个重复的问题,该问题得到了相同的答案,并附带其他详细信息:stackoverflow.com/a/37640136/228738
ShawnFeatherly

38

这并非完全可能,您想要的方式。在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(...)进行求值以生成字符串。它的参数不会在运行时评估,并且被视为不可访问的代码(但是不会发出“不可访问的代码”警告)。

更多信息可以在这里找到


在Nawfals上构建的C 3.0及更高版本的较早版本答案

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

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

更多详细信息在此博客文章中


3
链接断开了,尽管解决方案看起来很整洁。也许您可以在此处添加一些详细信息?
克莱门特

var variableName =“值”; 结果=(新的{variableName})。GetType()。GetProperties()[0] .Name; 新的{variableName}创建具有variableName =“ value”属性的匿名类,那样.GetProperties()[0] .Name获取此属性的名称
prampe 2016年

不用包装参数,您可以new { item }.GetType().GetProperties()[0].Name
mr5 '18

16

三种方式:

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再次缓慢。


9

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

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

辛苦了 它应该可以工作,但是对性能有很大影响,因为它意味着要读取源文件。我想知道是否有更有效的方法来完成相同的工作?
埃里克·厄勒

2
真恐怖,出色的工作!如果nameof让您查找堆栈的某一层会很好,因此nameof(x,1)可以为您提供调用方范围内作为参数传递x给当前方法的表达式的名称。
Ed Avis

4

不,但是只要您发现自己做这种非常复杂的事情,就可能需要重新考虑您的解决方案。请记住,代码应该比编写起来更容易阅读。


4

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

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

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


1
这正是我们在项目中所做的。我们使用异常来标记失败的方法前提条件。这些需要包含自变量的名称才有用。目前,我们Verify.SomeCondition(arg, nameof(arg))到处散布了!
pius

2

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


2

好吧,试试这个实用程序类,

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

1

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

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

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类称为error。我希望我的感冒可以根据我发送给它的变量名找出要使用的类。

再次感谢您的所有答复。


1

做这个

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帮助程序无法突出显示对已使用变量的引用。有点耗时,但通常更具可读性。
kernowcode 2014年

但是,如果您使用的是真正描述性的变量名,那么前者将非常有用。
kernowcode 2014年

如果您Named()2个变量引用同一实例,则此方法不起作用。看到这个例子
吉安·保罗2015年

没错,这是设计使唯一对象只能有一个名称的情况。如果实例已经命名,也许应该抛出异常。可以修改代码以克服此问题,但是获取对象的名称将必须返回列表。我认为,遵循良好的设计原则,目前的代码可以满足大多数代码需求。我真的不记得我上次创建相同实例的对象的时间。感谢您向我介绍.NET Fiddle,非常好。
kernowcode

0

简短的答案是……除非你真的有动力。

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

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


这是在2008年得到答复的,因此我不得不投票赞成,以消除负面投票
约翰C

0

不会。对您的字符串变量的引用将传递给函数-没有包含任何固有的元数据。即使是反思也不会使您脱颖而出-从单个引用类型向后工作并不能为您提供足够的信息来完成您需要做的事情。

最好回到这个图板上!

rp


0

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


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

-1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.