5

我经常想这样做:

public void Foo(Bar arg)
{
  throw new ArgumentException("Argument is incompatible with " + name(Foo));
}

因为如果我更改 Foo 的名称,IDE 也会重构我的错误消息,如果我将方法的名称(或任何其他类型的成员标识符)放在字符串文字中,则不会发生什么。我知道实现“名称”的唯一方法是使用反射,但我认为性能损失超过了可维护性增益,并且它不会涵盖所有类型的标识符。

括号之间的表达式的值可以在编译时计算(如 typeof),并通过更改语言规范优化为一个字符串文字。你认为这是一个有价值的功能吗?

PS:第一个示例看起来问题仅与异常有关,但事实并非如此。想想你可能想要引用类型成员标识符的每一种情况。您必须通过字符串文字来完成,对吗?

另一个例子:

[RuntimeAcessibleDocumentation(Description="The class " + name(Baz) +
  " does its job. See method " + name(DoItsJob) + " for more info.")]
public class Baz
{
  [RuntimeAcessibleDocumentation(Description="This method will just pretend " +
    "doing its job if the argument " + name(DoItsJob.Arguments.justPretend) +
    " is true.")]
  public void DoItsJob(bool justPretend) 
  {
    if (justPretend)
      Logger.log(name(justPretend) + "was true. Nothing done.");
  }
}

更新:这个问题是在 C# 6 之前发布的,但可能仍然与使用该语言早期版本的人相关。如果您使用的是 C# 6,请查看运算符,它与上面示例中nameof的运算符几乎相同。name

4

5 回答 5

11

好吧,你可以作弊并使用类似的东西:

public static string CallerName([CallerMemberName]string callerName = null)
{
    return callerName;
}

和:

public void Foo(Bar arg)
{
  throw new ArgumentException("Argument is incompatible with " + CallerName());
}

在这里,所有工作都由编译器完成(在编译时),因此如果您重命名方法,它将立即返回正确的内容。

于 2013-08-08T07:07:06.190 回答
6

如果您只是想要当前方法名称:MethodBase.GetCurrentMethod().Name

如果是类型typeof(Foo).Name

如果你想要一个变量/参数/字段/属性的名称,用Expression一棵小树

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

对于方法名称,它有点棘手:

public static readonly MethodInfo CreateDelegate = typeof(Delegate).GetMethod("CreateDelegate", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(Type), typeof(object), typeof(MethodInfo) }, null);

public static string GetMethodName<T>(Expression<Func<T>> exp)
{
    var body = exp.Body as UnaryExpression;

    if (body == null || body.NodeType != ExpressionType.Convert)
    {
        throw new ArgumentException();
    }

    var call = body.Operand as MethodCallExpression;

    if (call == null)
    {
        throw new ArgumentException();
    }

    if (call.Method != CreateDelegate)
    {
        throw new ArgumentException();
    }

    var method = call.Arguments[2] as ConstantExpression;

    if (method == null)
    {
        throw new ArgumentException();
    }

    MethodInfo method2 = (MethodInfo)method.Value;

    return method2.Name;
}

当你打电话给他们时,你必须指定兼容委托的类型(Action, Action<...>, Func<...>...)

string str5 = GetMethodName<Action>(() => Main);
string str6 = GetMethodName<Func<int>>(() => Method1);
string str7 = GetMethodName<Func<int, int>>(() => Method2);

或更简单地说,不使用表达式:-)

public static string GetMethodName(Delegate del)
{
    return del.Method.Name;
}

string str8 = GetMethodName((Action)Main);
string str9 = GetMethodName((Func<int>)Method1);
string str10 = GetMethodName((Func<int, int>)Method2);
于 2013-08-08T07:06:30.743 回答
1

最初的问题名为“如何在不将标识符写入 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);
      }
   }
}

这是一个测试文件,基于原始问题的前半部分:

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

为了使预处理程序的运行成为构建过程的一部分,您需要在两个地方修改 .csproj 文件。在第一部分插入这一行:

<UseHostCompilerIfAvailable>false</UseHostCompilerIfAvailable>

(这是可选的 - 请参阅此处https://stackoverflow.com/a/12163384/253938了解更多信息。)

并在 .csproj 文件的末尾用这些行替换一些被注释掉的行:

  <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

好的?

于 2013-08-10T02:51:52.617 回答
1

如前所述,由于方法名称位于异常的调用堆栈中,因此似乎没有必要对异常使用这种方法。

关于记录参数值问题中的另一个示例,这里似乎 PostSharp 将是一个不错的候选者,并且可能会允许许多您感兴趣的此类新功能。

看看PostSharp 上的这个页面,当我搜索如何使用 PostSharp 记录参数值(它涵盖)时,该页面出现了。摘自该页面:

你可以从一个方面获得很多有用的信息,但是有三个流行的类别:

  • 代码信息:函数名、类名、参数值等。这可以帮助您减少在锁定逻辑缺陷或边缘情况下的猜测
  • 性能信息:跟踪方法花费的时间
  • 异常:捕获选择/所有异常并记录有关它们的信息
于 2013-08-08T07:20:43.060 回答
0

C# 版本 6 引入了nameof运算符,其工作name方式与问题示例中描述的运算符类似,但有一些限制。以下是C# FAQ 博客中的一些示例和摘录:

(if x == null) throw new ArgumentNullException(nameof(x));

您可以在 nameof 表达式中放置更详细的点名,但这只是告诉编译器在哪里查找:只会使用最终标识符:

WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"

注意:自预览版构建以来,nameof 有一些小的设计更改。在预览中,不允许像上一个示例中的点表达式,其中 person 是范围内的变量。相反,您必须在类型中添加点。

于 2015-01-09T19:43:09.707 回答