如何引用标识符而不将其写入C#中的字符串文字?
我经常想这样做:如何引用标识符而不将其写入C#中的字符串文字?,c#,C#,我经常想这样做: public void Foo(Bar arg) { 抛出新ArgumentException(“参数与”+名称(Foo)不兼容”); } 因为如果我更改Foo的名称,IDE也会重构我的错误消息,如果我将方法的名称(或任何其他类型的成员标识符)放入字符串文本中,则不会发生什么。我所知道的实现“name”的唯一方法是使用反射,但我认为性能损失大于可维护性增益,并且它不会覆盖所有类型的标识符 括号之间表达式的值可以在编译时计算(如typeof)并通过更改语言规范优化为一个字符串文
public void Foo(Bar arg)
{
抛出新ArgumentException(“参数与”+名称(Foo)不兼容”);
}
因为如果我更改Foo的名称,IDE也会重构我的错误消息,如果我将方法的名称(或任何其他类型的成员标识符)放入字符串文本中,则不会发生什么。我所知道的实现“name”的唯一方法是使用反射,但我认为性能损失大于可维护性增益,并且它不会覆盖所有类型的标识符
括号之间表达式的值可以在编译时计算(如typeof)并通过更改语言规范优化为一个字符串文字。你认为这是一个有价值的特征吗
PS:第一个示例使问题看起来似乎只与异常有关,但事实并非如此。考虑您可能希望引用类型成员标识符的每种情况。你必须通过字符串文字来完成,对吗
另一个例子:
[RuntimeAcessibleDocumentation(Description=“The class”+名称(Baz)+
“执行其任务。有关详细信息,请参阅方法“+name(DoItsJob)+”。)]
公共类Baz
{
[RuntimeAcessibleDocumentation(Description=“此方法将只是假装”+
“如果参数”+名称(DoItsJob.Arguments.just假装),则执行其工作”+
“是真的。”)]
公共无效DoItsJob(bool just假装)
{
如果(只是假装)
Logger.log(name(just假装)+“为true,未执行任何操作”);
}
}
更新:这个问题是在C#6之前发布的,但可能仍然适用于使用该语言以前版本的人。如果您使用的是C#6,请检查
nameof
操作符,它的作用与上面示例中的name
操作符几乎相同。如果您只需要当前方法名称:MethodBase.GetCurrentMethod().name
如果是类型typeof(Foo).Name
如果需要变量/参数/字段/属性的名称,请使用一个小的表达式
树
public static string GetFieldName<T>(Expression<Func<T>> exp)
{
var body = exp.Body as MemberExpression;
if (body == null)
{
throw new ArgumentException();
}
return body.Member.Name;
}
string str = "Hello World";
string variableName = GetFieldName(() => str);
或者更简单地说,不使用表达式:-)
公共静态字符串GetMethodName(委托del)
{
返回del.Method.Name;
}
字符串str8=GetMethodName((操作)Main);
字符串str9=GetMethodName((Func)Method1);
字符串str10=GetMethodName((Func)Method2);
好吧,你可以作弊并使用类似的东西:
public static string CallerName([CallerMemberName]string callerName = null)
{
return callerName;
}
以及:
在这里,所有的工作都是由编译器(在编译时)完成的,因此如果重命名该方法,它将立即返回正确的内容。正如前面所述,对异常使用这种方法似乎是不必要的,因为方法名位于异常的调用堆栈中 与记录参数值的问题中的另一个示例相比,PostSharp似乎是一个很好的候选者,并且可能会允许您感兴趣的许多此类新功能 看看当我搜索如何使用PostSharp记录参数值(它涵盖了这些值)时出现了什么。摘录自该页: 您可以通过一个方面获得许多有用的信息,但有三种流行类别:
- 代码信息:函数名、类名、参数值等。这可以帮助您减少在确定逻辑缺陷或边缘情况时的猜测
- 性能信息:跟踪一个方法花费的时间
- 异常:捕获选择/所有异常并记录有关它们的信息
最初的问题名为“如何引用标识符而不将其写入C#中的字符串文字?”此答案不回答该问题,而是回答了“如何通过使用预处理器将其名称写入字符串文字来引用标识符?” 下面是一个非常简单的“概念验证”C#预处理器程序:
using System;
using System.IO;
namespace StackOverflowPreprocessor
{
/// <summary>
/// This is a C# preprocessor program to demonstrate how you can use a preprocessor to modify the
/// C# source code in a program so it gets self-referential strings placed in it.
/// </summary>
public class PreprocessorProgram
{
/// <summary>
/// The Main() method is where it all starts, of course.
/// </summary>
/// <param name="args">must be one argument, the full name of the .csproj file</param>
/// <returns>0 = OK, 1 = error (error message has been written to console)</returns>
static int Main(string[] args)
{
try
{
// Check the argument
if (args.Length != 1)
{
DisplayError("There must be exactly one argument.");
return 1;
}
// Check the .csproj file exists
if (!File.Exists(args[0]))
{
DisplayError("File '" + args[0] + "' does not exist.");
return 1;
}
// Loop to process each C# source file in same folder as .csproj file. Alternative
// technique (used in my real preprocessor program) is to read the .csproj file as an
// XML document and process the <Compile> elements.
DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(args[0]));
foreach (FileInfo fileInfo in directoryInfo.GetFiles("*.cs"))
{
if (!ProcessOneFile(fileInfo.FullName))
return 1;
}
}
catch (Exception e)
{
DisplayError("Exception while processing .csproj file '" + args[0] + "'.", e);
return 1;
}
Console.WriteLine("Preprocessor normal completion.");
return 0; // All OK
}
/// <summary>
/// Method to do very simple preprocessing of a single C# source file. This is just "proof of
/// concept" - in my real preprocessor program I use regex and test for many different things
/// that I recognize and process in one way or another.
/// </summary>
private static bool ProcessOneFile(string fileName)
{
bool fileModified = false;
string lastMethodName = "*unknown*";
int i = -1, j = -1;
try
{
string[] sourceLines = File.ReadAllLines(fileName);
for (int lineNumber = 0; lineNumber < sourceLines.Length - 1; lineNumber++)
{
string sourceLine = sourceLines[lineNumber];
if (sourceLine.Trim() == "//?GrabMethodName")
{
string nextLine = sourceLines[++lineNumber];
j = nextLine.IndexOf('(');
if (j != -1)
i = nextLine.LastIndexOf(' ', j);
if (j != -1 && i != -1 && i < j)
lastMethodName = nextLine.Substring(i + 1, j - i - 1);
else
{
DisplayError("Unable to find method name in line " + (lineNumber + 1) +
" of file '" + fileName + "'.");
return false;
}
}
else if (sourceLine.Trim() == "//?DumpNameInStringAssignment")
{
string nextLine = sourceLines[++lineNumber];
i = nextLine.IndexOf('\"');
if (i != -1 && i != nextLine.Length - 1)
{
j = nextLine.LastIndexOf('\"');
if (i != j)
{
sourceLines[lineNumber] =
nextLine.Remove(i + 1) + lastMethodName + nextLine.Substring(j);
fileModified = true;
}
}
}
}
if (fileModified)
File.WriteAllLines(fileName, sourceLines);
}
catch (Exception e)
{
DisplayError("Exception while processing C# file '" + fileName + "'.", e);
return false;
}
return true;
}
/// <summary>
/// Method to display an error message on the console.
/// </summary>
private static void DisplayError(string errorText)
{
Console.WriteLine("Preprocessor: " + errorText);
}
/// <summary>
/// Method to display an error message on the console.
/// </summary>
internal static void DisplayError(string errorText, Exception exceptionObject)
{
Console.WriteLine("Preprocessor: " + errorText + " - " + exceptionObject.Message);
}
}
}
要使预处理器程序的运行成为构建过程的一部分,可以在两个地方修改.csproj文件。在第一节中插入此行:
<UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable>
将被神奇地转化为说
string methodName = "Foo"; // Will be changed as necessary by preprocessor
OK?第6版C#引入了nameof
操作符,其工作原理与问题示例中描述的name
操作符类似,但有一些限制。以下是一些例子和摘录:
您可以在nameof表达式中添加更精细的虚线名称,但这只是告诉编译器在哪里查找:只使用最终标识符:
注意:自生成预览后,nameof的设计有一些小的更改。在预览中,不允许使用上一个示例中的虚线表达式,其中person是范围中的变量。相反,你必须在字体上打圆点
你为什么要这么做
Foo
无论如何都会在堆栈跟踪上。“我知道实现“name”的唯一方法是使用反射,但我认为性能损失大于可维护性增益”这个参数应该无关紧要,因为您的异常应该很少出现。@MarcinJuraszek想想其他情况,您可能希望通过名称引用成员(例如属性/xmldoc内的文档字符串、反射等)@TimSchmelter它很重要,因为这会限制“名称”的使用情况为了编写很少执行的代码,我在一个小小的C#预处理器程序中做了一些类似的事情,我作为每个构建的一部分运行,并且(如果需要)修改C#源文件。例如,将变量的名称复制到字符串赋值语句中。这很混乱,但可行。如果有兴趣的话,我会在回答中写下更多的细节。但这仍然是一种反思。这相对来说比较慢,但如果出现例外情况,那就不重要了。但不要在循环中执行此操作;)他要求编译时构造。这是在.NET4.5之前所能做的最好的构造。它将允许重构,而重构实际上是结束
public void Foo(Bar arg)
{
throw new ArgumentException("Argument is incompatible with " + CallerName());
}
using System;
using System.IO;
namespace StackOverflowPreprocessor
{
/// <summary>
/// This is a C# preprocessor program to demonstrate how you can use a preprocessor to modify the
/// C# source code in a program so it gets self-referential strings placed in it.
/// </summary>
public class PreprocessorProgram
{
/// <summary>
/// The Main() method is where it all starts, of course.
/// </summary>
/// <param name="args">must be one argument, the full name of the .csproj file</param>
/// <returns>0 = OK, 1 = error (error message has been written to console)</returns>
static int Main(string[] args)
{
try
{
// Check the argument
if (args.Length != 1)
{
DisplayError("There must be exactly one argument.");
return 1;
}
// Check the .csproj file exists
if (!File.Exists(args[0]))
{
DisplayError("File '" + args[0] + "' does not exist.");
return 1;
}
// Loop to process each C# source file in same folder as .csproj file. Alternative
// technique (used in my real preprocessor program) is to read the .csproj file as an
// XML document and process the <Compile> elements.
DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(args[0]));
foreach (FileInfo fileInfo in directoryInfo.GetFiles("*.cs"))
{
if (!ProcessOneFile(fileInfo.FullName))
return 1;
}
}
catch (Exception e)
{
DisplayError("Exception while processing .csproj file '" + args[0] + "'.", e);
return 1;
}
Console.WriteLine("Preprocessor normal completion.");
return 0; // All OK
}
/// <summary>
/// Method to do very simple preprocessing of a single C# source file. This is just "proof of
/// concept" - in my real preprocessor program I use regex and test for many different things
/// that I recognize and process in one way or another.
/// </summary>
private static bool ProcessOneFile(string fileName)
{
bool fileModified = false;
string lastMethodName = "*unknown*";
int i = -1, j = -1;
try
{
string[] sourceLines = File.ReadAllLines(fileName);
for (int lineNumber = 0; lineNumber < sourceLines.Length - 1; lineNumber++)
{
string sourceLine = sourceLines[lineNumber];
if (sourceLine.Trim() == "//?GrabMethodName")
{
string nextLine = sourceLines[++lineNumber];
j = nextLine.IndexOf('(');
if (j != -1)
i = nextLine.LastIndexOf(' ', j);
if (j != -1 && i != -1 && i < j)
lastMethodName = nextLine.Substring(i + 1, j - i - 1);
else
{
DisplayError("Unable to find method name in line " + (lineNumber + 1) +
" of file '" + fileName + "'.");
return false;
}
}
else if (sourceLine.Trim() == "//?DumpNameInStringAssignment")
{
string nextLine = sourceLines[++lineNumber];
i = nextLine.IndexOf('\"');
if (i != -1 && i != nextLine.Length - 1)
{
j = nextLine.LastIndexOf('\"');
if (i != j)
{
sourceLines[lineNumber] =
nextLine.Remove(i + 1) + lastMethodName + nextLine.Substring(j);
fileModified = true;
}
}
}
}
if (fileModified)
File.WriteAllLines(fileName, sourceLines);
}
catch (Exception e)
{
DisplayError("Exception while processing C# file '" + fileName + "'.", e);
return false;
}
return true;
}
/// <summary>
/// Method to display an error message on the console.
/// </summary>
private static void DisplayError(string errorText)
{
Console.WriteLine("Preprocessor: " + errorText);
}
/// <summary>
/// Method to display an error message on the console.
/// </summary>
internal static void DisplayError(string errorText, Exception exceptionObject)
{
Console.WriteLine("Preprocessor: " + errorText + " - " + exceptionObject.Message);
}
}
}
using System;
namespace StackOverflowDemo
{
public class DemoProgram
{
public class Bar
{}
static void Main(string[] args)
{}
//?GrabMethodName
public void Foo(Bar arg)
{
//?DumpNameInStringAssignment
string methodName = "??"; // Will be changed as necessary by preprocessor
throw new ArgumentException("Argument is incompatible with " + methodName);
}
}
}
<UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable>
<Target Name="BeforeBuild">
<Exec WorkingDirectory="D:\Merlinia\Trunk-Debug\Common\Build Tools\Merlinia Preprocessor\VS2012 projects\StackOverflowPreprocessor\bin" Command="StackOverflowPreprocessor.exe "$(MSBuildProjectFullPath)"" />
</Target>
string methodName = "??"; // Will be changed as necessary by preprocessor
string methodName = "Foo"; // Will be changed as necessary by preprocessor
(if x == null) throw new ArgumentNullException(nameof(x));
WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"