预处理器中的 C# 宏定义

Posted

技术标签:

【中文标题】预处理器中的 C# 宏定义【英文标题】:C# Macro definitions in Preprocessor 【发布时间】:2010-10-17 02:31:09 【问题描述】:

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

Console.WriteLine("foo");

【问题讨论】:

顺便说一下,对于这种特殊方法,您可以在 Visual Studio 中编写 cw 并按 Tab。 有一个用于 C# 的 Lisp 风格的宏处理器,名为 LeMP; Lisp 风格的宏远优于 C/C++ 宏。但在新版本的 C# 中,您可以在源文件顶部添加 using static System.Console; 后将 Console.WriteLine 缩短为 WriteLine 【参考方案1】:

不,C# 不支持像 C 这样的预处理器宏。另一方面,Visual Studio 有 snippets。 Visual Studio 的 sn-ps 是 IDE 的一项功能,在编辑器中扩展,而不是在由预处理器编译的代码中替换。

【讨论】:

@weberc2 糟糕的程序员不应该破坏我们其他人的优雅功能 @Jake 宏不是“优雅的功能”,它们是对糟糕的语言设计的补偿。 @weberc2 当同一个代码库必须针对多个编译时目标(例如 iosandroid、MacOSX 等)时,宏非常有用。缺少宏意味着您必须放很多#if/#elseif/#else pragma 之间的代码,当使用一些简单的宏时,可以以高效/优雅和可维护的方式为每个目标发出正确的代码。简而言之,当您必须包含无法在某些目标上编译的代码时,C 风格的宏非常有用。 @weberc2 这个“更好的解决方案”的问题在于它很重,并且将几行简单的代码分散到多个文件中,必须理解这些文件才能理解完整的解决方案。我不同意那更好。是的,C 预处理器被滥用到了痛苦的地步(我绝对是个滥用者!),但它在许多情况下也非常有用,可以让代码更简单、更容易理解。 @weberc2 我们在谈论两个不同的事情。我并不是说围绕大块代码的一堆#ifdefs 是好的。它不是。我是说,在有限的情况下,嵌入在#ifdef PLATFORM1 #elif PLATFORM2 ... 中的#define MACRO(x,y) 的司法使用可以用作工具箱中的工具。也许你也不同意这一点——在为预处理器进行谷歌搜索之后,世界上大多数人似乎也这样做了。但我确实遇到了this gem.【参考方案2】:

您可以使用 C 预处理器(如 mcpp)并将其安装到您的 .csproj 文件中。然后,您将源文件上的“构建操作”从 Compile 更改为 Preprocess 或您所称的任何内容。 只需像这样将 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>

您可能必须手动将至少一个文件上的编译更改为预处理(在文本编辑器中) - 然后在 Visual Studio 中应该可以选择“预处理”选项。

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

【讨论】:

您必须为“创新”解决方案和跳出框框思考而获得一些赞誉。但只是对任何阅读此“解决方案”的人的忠告,请不要这样做。有些事情只是错误,就是这样的黑客。这是其中之一。 @danpalmer 为什么将 CPP shellout 添加到 MyCommon.targets 只是错误? CPP 可以做的一些事情让语言变得异常困难。 IMO,更糟糕的是,开发人员在编写throw new ArgumentNullException("argumentName"); 时必须实际手动对参数名称进行字符串化。代码契约应该可以解决这个问题,但它需要一个使用.targets 的 IL 重写器,使用 CPP 调用应该不会更糟。 由于这篇文章是 2013 年的,所以我想提醒一下 C#6 的 nameof(argumentX)。 C# 6 和 7 添加了许多深刻而直接的解决方案来解决之前引起痛苦的琐碎问题,比如这个。老实说,我必须同意@danpalmer 的观点——是的,添加的这样的 hack 越多,就越不可能有人想要维护一个项目,甚至在几年后编译它。 在 C# 实现宏之前,这是迄今为止最好的解决方案,事实上,不费吹灰之力。我对此不够资格。大多数人不使用它的事实与它是完美解决方案无关。顺便说一句,恕我直言,这应该是普遍采用的。 @mireazma Intellisense 停止工作。所以这并不是一件容易的事【参考方案3】:

我用这个来避免Console.WriteLine(...)

public static void Cout(this string str, params object[] args)  
    Console.WriteLine(str, args);

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

"line 1".Cout();
"This 0 is an 1".Cout("sentence", "example");

它简洁而有点时髦。

【讨论】:

所以你不必输入Console.WriteLine(...)(这很长,必须经常输入)。您可以编写自己的方法来执行此操作,但恕我直言,使用字符串扩展名更优雅。 如果你使用制表符补全,时间不长。您正在创建两种方法来做完全相同的事情,这让其他开发人员感到困惑。此外,它并不优雅。你可能会在 Python 中看到这种东西,但在 C# 中很奇怪。 +1 用于制作扩展方法。虽然...由于 C# 不是 C++,我个人可能会称它为 .ToConsole() 而不是 Cout()。当然,“Cout”中的“C”表示控制台,而 .ToConsole() 更长,但 .ToConsole() 也是一种更常见的通用 .NET 模式,对于非 C++ 背景的人来说可能更有意义,和 Intellisense 会选择它,让您只需键入 .ToC 并按空格键即可完成它。 -1:这不仅是一个非常特殊的用例,不能轻易地普遍应用,而且它甚至也不是需要预处理宏的情况。 这不是预处理宏。这个问题的解决方案明确指出:“C# 不支持像 c 这样的预处理器宏”【参考方案4】:

虽然您不能编写宏,但在简化示例(如示例)时,C# 6.0 现在提供静态使用。这是 Martin Pernica 在his Medium article 上给出的示例:

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;
    
  

【讨论】:

【参考方案5】:

在 C# 中没有直接等效于 C 样式的宏,但 inlined 静态方法 - 有或没有 #if/#elseif/#else pragmas - 是您可以获得的最接近的方法:

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

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

【讨论】:

你可以在这些方法上使用[ConditionalAttribute("DEBUG")] 来达到与#ifs 相同的效果吗? @jpmc26 见***.com/questions/3788605/…【参考方案6】:

幸运的是,C# 没有 C/C++ 风格的预处理器 - 仅支持条件编译和 pragma(可能还有其他我想不起来的东西)。不幸的是,C# 没有元编程功能(这可能实际上在某种程度上与您的问题有关)。

【讨论】:

我会说编程语言的工作不是强制执行良好的风格。 C# 有 goto 并且开发人员有不使用它们的智能。这同样适用于宏,但有时我真的很想拥有它们! 示例:"Dispatcher.Invoke(delegate" 最好在您的 WPF 代码中作为宏。 @bytecode77 Goto 在 C#、C 和 C++ 中非常有用,可以跳出嵌套循环。像 Java 的 continue/break to label 这样的方法更智能,但 goto 就可以了。但是,宏在多平台支持由该语言处理的语言中没有用。 尽管我鼓励专业人士的 缺点 - 我看不出如何跳出循环优于布尔 quit 变量。一旦嵌套语句到位,代码就很难阅读和维护。 phpbreak 2..n; 语句,C# 没有。然而,C# 有足够的 LINQ 扩展,在许多情况下实际上不使用嵌套循环,从而使代码以不同的方式可读——我真的更喜欢这种方式。 我们在这里讨论了如何处理热循环的手写分析。幸运是一个非常糟糕的词选择。【参考方案7】:

将 C 宏转换为类中的 C# 静态方法。

【讨论】:

这不会为您提供 CPP 宏字符串化支持。宏很有用,因为它们将代码视为纯文本而不是代码。然而,OP 似乎并不真正想要/需要宏。出于他的目的,这将是一个完全有效(并且更好)的解决方案。【参考方案8】:

我建议你编写扩展程序,如下所示。

public static class WriteToConsoleExtension

   // Extension to all types
   public static void WriteToConsole(this object instance, 
                                     string format, 
                                     params object[] data)
   
       Console.WriteLine(format, data);
   


class Program

    static void Main(string[] args)
    
        Program p = new Program();
        // Usage of extension
        p.WriteToConsole("Test 0, 1", DateTime.Now, 1);
    

希望这会有所帮助(而且还不算太晚:))

【讨论】:

对我来说这只是令人困惑 Extensionitis,我有时这么称呼它。为我构建 API DLL 通常会导致越来越多的扩展而不是方法。例如。 ValidateUrl(this string) - 到目前为止,我更喜欢在课堂上使用的东西,因为这都会使智能感知膨胀(尤其是 this object),并且有时会让人难以找到此类方法。拥有Validate.Url(string) 不会破坏代码,而且显然很容易被其他人找到和使用。 我同意你的看法【参考方案9】:

使用 lambdas

void print(string x) => Trace.WriteLine(x);
void println(string x) => Console.WriteLine(x);
void start(string x) => Process.Start(x);

void done() => Trace.WriteLine("Done");
void hey() => Console.WriteLine("hey");

【讨论】:

【参考方案10】:

由于 C# 7.0 支持 using static 指令和 Local functions,因此大多数情况下不需要预处理器宏。

【讨论】:

不一样——本地函数实际上在你所看到的后面创建了正常的函数,自动带有许多参数

以上是关于预处理器中的 C# 宏定义的主要内容,如果未能解决你的问题,请参考以下文章

C中的预编译宏定义

C语言学习笔记--C语言中的宏定义

第六章 预处理器

笔记3:预处理器-宏定义

VS中添加预处理宏的方法

什么是C宏有用?