如何引用标识符而不将其写入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 &quot;$(MSBuildProjectFullPath)&quot;" />
  </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"