获取参数的调用变量名
Getting the calling variable name of a parameter
关于问题 Get the name of parameters from a calling method and Finding the Variable Name passed to a Function in C# 我仍在寻找一种方法来定义 WhatDoesTheAnimalSay_WANTED
方法:我想知道用作参数的变量的名称:
public class Farm
{
public readonly string Cow = "Muuuuhh";
public string Cat { get; set; }
public void MainFunction()
{
var dog = "WauWau";
var kiwi = new Bird("QueeeekQueeek");
Cat = "Miiiaaauuuu";
// This one works but looks kinda ugly and is cumbersome when used
WhatDoesTheAnimalSay(nameof(dog), dog);
WhatDoesTheAnimalSay(nameof(Cow), Cow);
WhatDoesTheAnimalSay(nameof(Cat), Cat);
WhatDoesTheAnimalSay(nameof(kiwi), kiwi);
WhatDoesTheAnimalSay_WRONG1(dog);
WhatDoesTheAnimalSay_WRONG2(dog);
WhatDoesTheAnimalSay_WANTED(dog);
WhatDoesTheAnimalSay_WANTED(Cow);
}
public void WhatDoesTheAnimalSay<T>(string name, T says)
{
MessageBox.Show("The " + name + " says: " + says);
// Shows i.e.: The dog says: WauWau
}
public void WhatDoesTheAnimalSay_WRONG1<T>(T says)
{
MessageBox.Show("The " + nameof(says) + " says: " + says);
// Shows: The says says: WauWau
}
public void WhatDoesTheAnimalSay_WRONG2<T>(T says, [CallerMemberName] string name = null)
{
MessageBox.Show("The " + name + " says: " + says);
// Shows: The MainFunction says: WauWau
}
public void WhatDoesTheAnimalSay_WANTED<T>(T says /*, ?????*/)
{
MessageBox.Show("The " /*+ ?????*/ + " says: " + says);
// Shows: The dog says: WauWau
}
}
// Just here to show it should work with a class as well.
public class Bird
{
public string Says { get; } //readonly
public Bird(string says) {
Says = says;
}
public override string ToString()
{
return Says;
}
}
在现实生活中,我在使用自定义 reader.Read...
和 writer.Write....
方法在我的 class 中实现 IXmlSerializable
接口时需要这个。
所以,不幸的是,我不能引入新包装class、接口或更改动物名称的位置或它所说的已保存。这意味着它必须与 classes 以及 纯字符串、整数、小数……变量、字段或属性 一起使用。
换句话说(不是粗鲁的方式):不要改变动物的定义方式,只需改变 WhatDoesTheAnimalSay_WANTED
方法...
编辑:
由于你们中有些人想知道这个示例的真实用例,我在这里向你们介绍我如何在 xml 文件中存储和读取数据。真正的数据库对象当然更大,所有子 classes(如 Fitter
)也使用相同的扩展方法实现 IXmlSerializable
接口。
// This is the Database Class which stores all the data
public class Database : IXmlSerializable
{
// The list of all building sites managed
public List<BuildingSite> BuildingSites { get; set; }
// The list of all fitters working for the company
public List<Fitter> Fitters { get; set; }
private readonly int DatabaseVersion = 1;
// Write implementation of the IXmlSerializable inteface
public void WriteXml(XmlWriter writer)
{
// Writing all Data into the xml-file
writer.WriteElementInt(nameof(DatabaseVersion), DatabaseVersion);
writer.WriteElementList(nameof(BuildingSites), BuildingSites);
writer.WriteElementList(nameof(Fitters), Fitters);
}
public void ReadXml(XmlReader reader)
{
// Do the reading here
}
public XmlSchema GetSchema() { return null; }
}
public class XmlExtensions
{
// Writing a list into the xml-file
public static void WriteElementList<T>(this XmlWriter writer, string elementName, IEnumerable<T> items)
{
var list = items is List<T> ? items : items.ToList();
// The XML-Element should have the name of the variable in Database!!!
writer.WriteStartElement(elementName);
writer.WriteAttributeString("count", list.Count().ToString());
var serializer = new XmlSerializer(typeof(T));
list.ForEach(o => serializer.Serialize(writer, o, XmlHelper.XmlNamespaces));
writer.WriteEndElement();
}
public static void WriteElementInt(this XmlWriter writer, string elementName, int toWrite)
{
// The XMLElement should have the name of the variable in Database!!!
writer.WriteElementString(elementName, toWrite.ToString(CultureInfo.InvariantCulture));
}
// More here....
}
}
恐怕目前没有办法实现你想要的。局部变量名称不可用于被调用函数。
有替代品,你列出了一些。另一种选择是将变量名称作为 属性 放在 class 中。无论如何,这将是更好的设计,因为应用程序中的数据不应该依赖于变量名。如果有人重命名所有变量,您的应用程序应该以相同的方式工作。
其他语言如 C/C++ 系列使用预处理器宏来实现此目的,但它们在 C# 中不可用。您可能可以在 C++/CLI 中实现这一点,但只能在内部没有选择将此类宏导出到编译单元 and/or 程序集之外的选项。
所以 否,抱歉,即使使用 C# 6 及其新功能,这也是不可能的。它不应该是。想想你的设计,你可能会想出一个更好的。
你可以使用一个以Expression<Func<object>>
作为参数的方法:
public void WhatDoesTheAnimalSay_WANTED(Expression<Func<object>> expression)
{
var body = (MemberExpression)expression.Body;
var variableName = body.Member.Name;
var func = expression.Compile();
var variableValue = func();
MessageBox.Show("The "+ variableName + " says: " + variableValue);
}
使用这种方法可以处理各种变量(静态成员、实例成员、参数、局部变量等),也可以处理属性。
这样称呼它:
WhatDoesTheAnimalSay_WANTED(() => dog)
WhatDoesTheAnimalSay_WANTED(() => Cow)
WhatDoesTheAnimalSay_WANTED(() => Cat)
WhatDoesTheAnimalSay_WANTED(() => kiwi)
常量是不可能的,因为编译器会用编译时给定的值替换常量占位符:
const string constValue = "Constant Value";
WhatDoesTheAnimalSay_WANTED(() => constValue)
将转换为
WhatDoesTheAnimalSay_WANTED(() => "Constant Value")
制作 ConstantExpression
类型的 expression.Body
,这会在运行时产生异常。
所以你必须小心你提供给那个方法的表达式。
补充说明
您可以从下面的评论中注意到,使用 lambda 表达式来收集变量名似乎存在争议。
正如@CodeCaster 在 中指出的那样,没有正式规定编译器需要在匿名包装class 中为捕获的成员取一个局部变量的同名。
然而,我在Expression<TDelegate>的评论中发现了这一点:
The ability to treat expressions as data structures enables APIs to receive user code in a format that can be inspected, transformed, and processed in a custom manner.
对我来说,这表明表达式树正是为这样的目的而设计的。
尽管 Microsoft 可能出于某种原因更改此行为,但似乎没有这样做的合乎逻辑的需要。依靠 principle of least astonishment 我会说可以安全地假设 ()=> dog
的 Body
属性 是 MemberExpression
类型的表达式,body.Member.Name
解析为 dog.
如果 Body
还需要其他类型,则该方法必须多做一些。而且这在某些情况下也可能不起作用。
关于问题 Get the name of parameters from a calling method and Finding the Variable Name passed to a Function in C# 我仍在寻找一种方法来定义 WhatDoesTheAnimalSay_WANTED
方法:我想知道用作参数的变量的名称:
public class Farm
{
public readonly string Cow = "Muuuuhh";
public string Cat { get; set; }
public void MainFunction()
{
var dog = "WauWau";
var kiwi = new Bird("QueeeekQueeek");
Cat = "Miiiaaauuuu";
// This one works but looks kinda ugly and is cumbersome when used
WhatDoesTheAnimalSay(nameof(dog), dog);
WhatDoesTheAnimalSay(nameof(Cow), Cow);
WhatDoesTheAnimalSay(nameof(Cat), Cat);
WhatDoesTheAnimalSay(nameof(kiwi), kiwi);
WhatDoesTheAnimalSay_WRONG1(dog);
WhatDoesTheAnimalSay_WRONG2(dog);
WhatDoesTheAnimalSay_WANTED(dog);
WhatDoesTheAnimalSay_WANTED(Cow);
}
public void WhatDoesTheAnimalSay<T>(string name, T says)
{
MessageBox.Show("The " + name + " says: " + says);
// Shows i.e.: The dog says: WauWau
}
public void WhatDoesTheAnimalSay_WRONG1<T>(T says)
{
MessageBox.Show("The " + nameof(says) + " says: " + says);
// Shows: The says says: WauWau
}
public void WhatDoesTheAnimalSay_WRONG2<T>(T says, [CallerMemberName] string name = null)
{
MessageBox.Show("The " + name + " says: " + says);
// Shows: The MainFunction says: WauWau
}
public void WhatDoesTheAnimalSay_WANTED<T>(T says /*, ?????*/)
{
MessageBox.Show("The " /*+ ?????*/ + " says: " + says);
// Shows: The dog says: WauWau
}
}
// Just here to show it should work with a class as well.
public class Bird
{
public string Says { get; } //readonly
public Bird(string says) {
Says = says;
}
public override string ToString()
{
return Says;
}
}
在现实生活中,我在使用自定义 reader.Read...
和 writer.Write....
方法在我的 class 中实现 IXmlSerializable
接口时需要这个。
所以,不幸的是,我不能引入新包装class、接口或更改动物名称的位置或它所说的已保存。这意味着它必须与 classes 以及 纯字符串、整数、小数……变量、字段或属性 一起使用。
换句话说(不是粗鲁的方式):不要改变动物的定义方式,只需改变 WhatDoesTheAnimalSay_WANTED
方法...
编辑:
由于你们中有些人想知道这个示例的真实用例,我在这里向你们介绍我如何在 xml 文件中存储和读取数据。真正的数据库对象当然更大,所有子 classes(如 Fitter
)也使用相同的扩展方法实现 IXmlSerializable
接口。
// This is the Database Class which stores all the data
public class Database : IXmlSerializable
{
// The list of all building sites managed
public List<BuildingSite> BuildingSites { get; set; }
// The list of all fitters working for the company
public List<Fitter> Fitters { get; set; }
private readonly int DatabaseVersion = 1;
// Write implementation of the IXmlSerializable inteface
public void WriteXml(XmlWriter writer)
{
// Writing all Data into the xml-file
writer.WriteElementInt(nameof(DatabaseVersion), DatabaseVersion);
writer.WriteElementList(nameof(BuildingSites), BuildingSites);
writer.WriteElementList(nameof(Fitters), Fitters);
}
public void ReadXml(XmlReader reader)
{
// Do the reading here
}
public XmlSchema GetSchema() { return null; }
}
public class XmlExtensions
{
// Writing a list into the xml-file
public static void WriteElementList<T>(this XmlWriter writer, string elementName, IEnumerable<T> items)
{
var list = items is List<T> ? items : items.ToList();
// The XML-Element should have the name of the variable in Database!!!
writer.WriteStartElement(elementName);
writer.WriteAttributeString("count", list.Count().ToString());
var serializer = new XmlSerializer(typeof(T));
list.ForEach(o => serializer.Serialize(writer, o, XmlHelper.XmlNamespaces));
writer.WriteEndElement();
}
public static void WriteElementInt(this XmlWriter writer, string elementName, int toWrite)
{
// The XMLElement should have the name of the variable in Database!!!
writer.WriteElementString(elementName, toWrite.ToString(CultureInfo.InvariantCulture));
}
// More here....
}
}
恐怕目前没有办法实现你想要的。局部变量名称不可用于被调用函数。
有替代品,你列出了一些。另一种选择是将变量名称作为 属性 放在 class 中。无论如何,这将是更好的设计,因为应用程序中的数据不应该依赖于变量名。如果有人重命名所有变量,您的应用程序应该以相同的方式工作。
其他语言如 C/C++ 系列使用预处理器宏来实现此目的,但它们在 C# 中不可用。您可能可以在 C++/CLI 中实现这一点,但只能在内部没有选择将此类宏导出到编译单元 and/or 程序集之外的选项。
所以 否,抱歉,即使使用 C# 6 及其新功能,这也是不可能的。它不应该是。想想你的设计,你可能会想出一个更好的。
你可以使用一个以Expression<Func<object>>
作为参数的方法:
public void WhatDoesTheAnimalSay_WANTED(Expression<Func<object>> expression)
{
var body = (MemberExpression)expression.Body;
var variableName = body.Member.Name;
var func = expression.Compile();
var variableValue = func();
MessageBox.Show("The "+ variableName + " says: " + variableValue);
}
使用这种方法可以处理各种变量(静态成员、实例成员、参数、局部变量等),也可以处理属性。
这样称呼它:
WhatDoesTheAnimalSay_WANTED(() => dog)
WhatDoesTheAnimalSay_WANTED(() => Cow)
WhatDoesTheAnimalSay_WANTED(() => Cat)
WhatDoesTheAnimalSay_WANTED(() => kiwi)
常量是不可能的,因为编译器会用编译时给定的值替换常量占位符:
const string constValue = "Constant Value";
WhatDoesTheAnimalSay_WANTED(() => constValue)
将转换为
WhatDoesTheAnimalSay_WANTED(() => "Constant Value")
制作 ConstantExpression
类型的 expression.Body
,这会在运行时产生异常。
所以你必须小心你提供给那个方法的表达式。
补充说明
您可以从下面的评论中注意到,使用 lambda 表达式来收集变量名似乎存在争议。
正如@CodeCaster 在
然而,我在Expression<TDelegate>的评论中发现了这一点:
The ability to treat expressions as data structures enables APIs to receive user code in a format that can be inspected, transformed, and processed in a custom manner.
对我来说,这表明表达式树正是为这样的目的而设计的。
尽管 Microsoft 可能出于某种原因更改此行为,但似乎没有这样做的合乎逻辑的需要。依靠 principle of least astonishment 我会说可以安全地假设 ()=> dog
的 Body
属性 是 MemberExpression
类型的表达式,body.Member.Name
解析为 dog.
如果 Body
还需要其他类型,则该方法必须多做一些。而且这在某些情况下也可能不起作用。