预处理器中的C#宏定义

64 c# macros c-preprocessor

C#能否像使用预处理器语句在C编程语言中那样定义宏?我想简化某些重复语句的常规输入,如下所示:

Console.WriteLine("foo");
Run Code Online (Sandbox Code Playgroud)

And*_*are 48

不,C#不支持像C这样的预处理器宏.另一方面,Visual Studio有片段.Visual Studio的代码片段是IDE的一个功能,在编辑器中进行了扩展,而不是在预处理器的编译代码中替换.

  • 当相同的代码库必须针对多个编译时目标(如iOS,Android,MacOSX等)时,@ weberc2宏非常有用.缺少宏意味着你必须在`#if/#asseif之间放置大量代码/#else`pragma当使用一些简单的宏时,可以以高效/优雅和可维护的方式为每个目标发出正确的代码.简而言之,当您必须包含无法在某些目标上编译的代码时,C风格的宏非常有用. (61认同)
  • @ weberc2糟糕的程序员不应该破坏我们其他人的优雅功能 (23认同)
  • @Jake Macros不是"优雅的功能",它们是对糟糕的语言设计的补偿. (19认同)
  • @ weberc2这个"更好的解决方案"的问题在于它很重,并且可以在多个文件上传播一些简单的代码行,这些代码必须全部被理解才能理解完整的解决方案.我不同意这更好.是的,C预处理器被滥用到折磨点(我绝对是滥用者!),但在许多情况下它也非常有用,使代码更简单,更容易理解. (17认同)
  • @ weberc2我们谈论的是两件不同的事情.我不是说围绕大块代码的一堆`#ifdefs`是好的.不是.我说司法使用`#ifdef PLATFORM1 #elif PLATFORM2 ......中嵌入的`#define MACRO(x,y)`在有限的情况下可以作为工具箱中的工具使用.也许你不同意这一点 - 在谷歌搜索保护预处理器后似乎世界上大多数人也这样做了.但我确实遇到了[这个宝石.](http://stackoverflow.com/a/653028/9648) (5认同)
  • @JohnnyLambada 更好的解决方案是在公共接口后面抽象您的平台相关代码,并使用 CMake 和朋友来确保为给定目标编译正确的代码。见鬼,你甚至可以在 C 中做到这一点。 (4认同)
  • @JohnnyLambada如果您真的想在尽量减少痛苦的情况下使用预处理器,请不要使用您的平台逻辑定义宏,否则您将不得不在整个代码库中处理调试/读取宏.相反,在尽可能少的文件中,定义封装预处理器逻辑的函数,这样您就不必在其他任何地方担心它.但是,使用构建系统有条件地编译文件而不是预处理器仍然会更好. (4认同)
  • @Jake这不是因为你从来没有遇到过这样的情况,即绝对不需要这种情况.这实际上是什么让我来到这里,让我告诉你你的评论是多么无用...... (3认同)
  • 我知道这个帖子已经沉寂多年了,但我不得不插话一下。宏可以做一些事情,但我认为没有任何替代解决方案 - C/C++ 风格的宏可以做函数调用根本无法做的事情 - 即强制将代码内联到函数中,并可以访问它们作用域的完整堆栈帧结合这样的事实,宏还允许在编译时生成代码,因为宏参数可以转换为带引号的字符串并连接,以及使用“普通”,这提供了在 C/C++ 中编写代码的可能性,AFAIK C# (3认同)
  • 对于那些假装宏是坏的:C#项目使用大量的"宏",它们只是以在编译器外部运行的代码生成器的形式出现.但是,外部代码生成器使构建复杂化并创建更多非标准DSL.我宁愿有能力编写编译器宏来发出重复的代码块,而不是编写外部代码生成器.宏也可以做很多事情,函数不能.(提取__line__,生成样板)说,我更喜欢我的宏是语法宏,更像是Nemerle或Boo,而不是C预处理器. (3认同)
  • @JohnnyLambada 我强烈不同意。它实际上与您所描述的完全相反:它将大量预处理器垃圾分散在许多文件(基本上与目标问题无关的文件)中,并将它们移动到几个负责目标配置的文件中。我不知道怎么会有人认为散布着 `#ifdef WIN32...` 的代码比将其条件编译问题隔离到几个理智的地方的代码更具可读性...... (2认同)
  • @ Virus721它可能看起来没用了.我最初回复了几年前[weberc2](http://stackoverflow.com/users/483347/weberc2)发表的评论,他从此删除了它.他最初的评论是,"感谢上帝没有宏. - weberc2 12月25日12:27" (2认同)
  • 宏允许使用超快速但丑陋的内联来优化代码的性能关键部分。考虑对“Bgra32”像素进行一些计算:任何将此类像素拆分为 ARGB 分量的函数都将比直接编码的按位运算慢得多。但是这种操作的直接编码比使用像`_G(pixel)`这样的宏可读性要差得多。 (2认同)
  • 代码片段在编辑时而不是在编译时插入代码,因此编辑代码片段不会在重新编译时更改代码。 (2认同)
  • 片段不能替代宏。我不知道为什么这么多人赞成这个...... (2认同)

小智 36

您可以使用C预处理器(如mcpp)并将其装入.csproj文件中.然后,您可以在源文件中编译"构建操作",从"编译"到"预处理"或您调用的任何内容.只需将BeforBuild添加到您的.csproj中,如下所示:

  <Target Name="BeforeBuild" Inputs="@(Preprocess)" Outputs="@(Preprocess->'%(Filename)_P.cs')">
<Exec Command="..\Bin\cpp.exe @(Preprocess) -P -o %(RelativeDir)%(Filename)_P.cs" />
<CreateItem Include="@(Preprocess->'%(RelativeDir)%(Filename)_P.cs')">
  <Output TaskParameter="Include" ItemName="Compile" />
</CreateItem>
Run Code Online (Sandbox Code Playgroud)

您可能必须在至少一个文件(在文本编辑器中)手动更改编译到预处理 - 然后在Visual Studio中可以选择"预处理"选项.

我知道宏被严重过度使用和误用,但是如果不是更糟的话,将它们完全删除也同样糟糕.宏使用的典型示例是NotifyPropertyChanged.每个必须手动重写这段代码的程序员都知道没有宏会有多痛苦.

  • 您必须获得一些"创新"解决方案,以及开箱即用的思考.但对于阅读这个"解决方案"的人来说,只是一句忠告,请不要这样做.有些东西只是*错误*,就是这样的黑客攻击.这是其中之一. (13认同)
  • @danpalmer为什么在'MyCommon.targets`中添加CPP shellout只是*错*?CPP可以做的一些事情使语言难以置信.IMO,更糟糕的是开发人员在编写`throw new ArgumentNullException("argumentName");`时必须实际手动字符串化参数名称.代码契约本来应该解决这个问题,但它需要一个IL重写器使用`.targets`进行操作,对于CPP调用来说应该不会更糟. (11认同)

ant*_*ell 28

我用它来避免Console.WriteLine(...):

public static void Cout(this string str, params object[] args) { 
    Console.WriteLine(str, args);
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用以下内容:

"line 1".Cout();
"This {0} is an {1}".Cout("sentence", "example");
Run Code Online (Sandbox Code Playgroud)

它简洁而有点时髦.

  • 如果使用制表符完成则不久.您正在创建两种完全相同的方法,这会让其他开发人员感到困惑.而且,它并不优雅; 你可能会在Python中看到这种东西,但它在C#中很奇怪. (6认同)
  • 这不是预处理器宏.这个问题的解决方案明确指出:"C#不支持像c这样的预处理器宏" (6认同)
  • +1用于制作扩展方法.虽然...因为C#不是C++,我个人可能会称它为.ToConsole()而不是Cout().当然,"Cout"中的"C"表示控制台,而.ToConsole()更长,但.ToConsole()也是一种更常见的通用.NET模式,对于不是来自C++背景的人来说可能更有意义,*并且*Intellisense将接收它并让你只需键入.ToC并点击空格键即可完成它. (4认同)
  • -1:这不仅是一个非常具体的用例,通常不能轻易应用,但它甚至也不是预处理器宏的保证. (4认同)
  • 因此,您不必键入`Console.WriteLine(...)`(这需要经常输入很长时间).你可以编写自己的方法来做到这一点,但使用字符串扩展是一个更优雅的恕我直言. (2认同)

sra*_*boy 12

虽然你不能编写宏,但在简化你的例子之类的东西时,C#6.0现在提供了静态用法.以下是Martin Pernica在他的Medium文章中给出的例子:

using static System.Console; // Note the static keyword

namespace CoolCSharp6Features
{
  public class Program
  {
    public static int Main(string[] args)
    {
      WriteLine("Hellow World without Console class name prefix!");

      return 0;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


Bin*_*man 6

没有直接等价于C风格的宏在C#中,但inlined静态方法-用或不用#if/ #elseif/ #else编译指示-是你可以得到的最接近:

        /// <summary>
        /// Prints a message when in debug mode
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void Log(object message) {
#if DEBUG
            Console.WriteLine(message);
#endif
        }

        /// <summary>
        /// Prints a formatted message when in debug mode
        /// </summary>
        /// <param name="format">A composite format string</param>
        /// <param name="args">An array of objects to write using format</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void Log(string format, params object[] args) {
#if DEBUG
            Console.WriteLine(format, args);
#endif
        }

        /// <summary>
        /// Computes the square of a number
        /// </summary>
        /// <param name="x">The value</param>
        /// <returns>x * x</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static double Square(double x) {
            return x * x;
        }

        /// <summary>
        /// Wipes a region of memory
        /// </summary>
        /// <param name="buffer">The buffer</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void ClearBuffer(ref byte[] buffer) {
            ClearBuffer(ref buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Wipes a region of memory
        /// </summary>
        /// <param name="buffer">The buffer</param>
        /// <param name="offset">Start index</param>
        /// <param name="length">Number of bytes to clear</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static unsafe void ClearBuffer(ref byte[] buffer, int offset, int length) {
            fixed(byte* ptrBuffer = &buffer[offset]) {
                for(int i = 0; i < length; ++i) {
                    *(ptrBuffer + i) = 0;
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

这可以完美地用作宏,但有一个缺点:标记为inlined的方法将像其他“常规”方法一样复制到程序集的反射部分。

  • 您可以在这些方法上使用“[ConditionalAttribute("DEBUG")]”来达到与“#if”相同的效果吗? (3认同)