How to refer to an identifier without writing it into a string literal in C#?

前端 未结 5 803
甜味超标
甜味超标 2020-12-18 06:45

I often want to do this:

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


        
5条回答
  •  南笙
    南笙 (楼主)
    2020-12-18 07:22

    The original question is named "How to refer to an identifier without writing it into a string literal in C#?" This answer does not answer that question, instead, it answers the question "How to refer to an identifier by writing its name into a string literal using a preprocessor?"

    Here is a very simple "proof of concept" C# preprocessor program:

    using System;
    using System.IO;
    
    namespace StackOverflowPreprocessor
    {
       /// 
       /// 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.
       /// 
       public class PreprocessorProgram
       {
          /// 
          /// The Main() method is where it all starts, of course. 
          /// 
          /// must be one argument, the full name of the .csproj file
          /// 0 = OK, 1 = error (error message has been written to console)
          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  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
          }
    
    
          /// 
          /// 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.
          /// 
          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;
          }
    
    
          /// 
          /// Method to display an error message on the console. 
          /// 
          private static void DisplayError(string errorText)
          {
             Console.WriteLine("Preprocessor: " + errorText);
          }
    
    
          /// 
          /// Method to display an error message on the console. 
          /// 
          internal static void DisplayError(string errorText, Exception exceptionObject)
          {
             Console.WriteLine("Preprocessor: " + errorText + " - " + exceptionObject.Message);
          }
       }
    }
    

    And here's a test file, based on the first half of the original question:

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

    To make the running of the preprocessor program a part of the build process you modify the .csproj file in two places. Insert this line in the first section:

    false
    

    (This is optional - see here https://stackoverflow.com/a/12163384/253938 for more information.)

    And at the end of the .csproj file replace some lines that are commented-out with these lines:

      
        
      
    

    Now when you recompile the test program the line that says

         string methodName = "??";  // Will be changed as necessary by preprocessor
    

    will be magically converted to say

         string methodName = "Foo";  // Will be changed as necessary by preprocessor
    

    OK?

提交回复
热议问题