预处理器中的C#宏定义


75

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

Console.WriteLine("foo");

29
顺便说一句,对于这种特定方法,您可以cw在Visual Studio中编写并按Tab。
2013年

2
C#有一个Lisp风格的宏处理器,称为LeMP;Lisp风格的宏比C / C ++宏优越得多。但是在新版本的C#中,您可以在源文件顶部添加后缩短Console.WriteLine到。WriteLineusing static System.Console;
Qwertie

Answers:


54

不,C#不支持像C这样的预处理器宏。另一方面,Visual Studio具有摘要。Visual Studio的片段是IDE的功能,并且在编辑器中进行了扩展,而不是在编译时被预处理器替换。


26
@ weberc2不好的程序员不应该破坏我们其他人的优雅功能
Jake

22
@Jake Macros不是“优雅的功能”,它们是对糟糕的语言设计的补偿。
weberc2

70
当同一代码库必须针对多个编译时目标(例如iOS,Android,MacOSX等)时,@ weberc2宏非常有用。缺少宏意味着#if/#elseif/#else使用一些简单的宏时,您必须在编译指示之间放置大量代码。可以以有效/优雅和可维护的方式为每个目标发出适当的代码。简而言之,当您必须包含根本无法在某些目标上编译的代码时,C样式宏非常有用。
JohnnyLambada 2013年

19
@ weberc2这个“更好的解决方案”的问题在于它很重,并且可能在多个文件中散布一些简单的代码行,必须理解所有这些文件才能理解完整的解决方案。我不同意那会更好。是的,C预处理器被滥用到了折磨的地步(我绝对是滥用者!),但是在许多情况下,使代码更简单和易于理解也非常有用。
JohnnyLambada 2013年

6
@ weberc2我们正在谈论两件事。我并不是说#ifdefs周围的一大堆代码很好。不是。我是说,在有限的情况下,将#define MACRO(x,y)嵌入式#ifdef PLATFORM1 #elif PLATFORM2 ...工具用作工具箱中的工具可能会很有用。也许您也不同意这一点-在进行Google搜索以捍卫预处理器之后,世界上大多数地区似乎也这样做。但是我确实遇到了这个宝石。
JohnnyLambada 2013年

44

您可以使用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>

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

我知道宏被严重过度使用和滥用,但完全删除它们同样糟糕,甚至更糟。宏用法的经典示例是NotifyPropertyChanged。每个必须手动重写此代码数千次的程序员都知道,没有宏,它是多么痛苦。


15
您必须为“创新”解决方案和突破性思维而获得荣誉。但是,对于阅读此“解决方案”的任何人来说,只要提个建议,请不要这样做。有些事情是错误的,就是这样的hack。这就是其中之一。
danpalmer

15
@danpalmer为什么添加CPP ShelloutMyCommon.targets只是错误的?CPP可以完成某些事情,使该语言变得异常困难。IMO,更糟糕的是,开发人员在编写时实际上必须手动将参数名称字符串化throw new ArgumentNullException("argumentName");。本来应该通过代码合同来解决此问题,但需要使用IL重写器,而使用.targetsCPP调用则更糟。
2014年

由于该帖子来自2013年,我想提醒一下C#6的nameof(argumentX)。C#6和7为诸如此类的较早引起麻烦的琐碎问题添加了许多深刻而直接的解决方案。老实说,我必须同意@danpalmer-是的,添加的此类黑客越多,几年后任何人维护项目甚至编译项目的可能性就越小。
bytecode77

1
在C#实现宏之前,这是迄今为止最好的解决方案,事实上,这是一个明智的选择。我不能满足这个要求。大多数人不使用它的事实与它是完美的解决方案无关。顺便说一句,恕我直言,这应该被普遍采用。
mireazma

1
@mireazma Intellisense虽然停止工作。因此,这并不完全是没有道理的
Ayxan Haqverdili

28

我用它来避免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");

简洁而时髦。


2
因此,您不必键入Console.WriteLine(...)(必须经常键入很长的时间)。您可以编写自己的方法来执行此操作,但是使用字符串扩展名稍微有点优雅,恕我直言。
anthonybell

6
如果您使用制表符补全,就不会太久。您正在创建两种完全相同的方法,这会使其他开发人员感到困惑。此外,它并不更优雅。您可能会在Python中看到这种情况,但在C#中却很奇怪。
mpen

4
+1用于制定扩展方法。尽管...由于C#不是C ++,所以我个人可能会称其为.ToConsole()而不是Cout()。当然,“ Cout”中的“ C”表示控制台,.ToConsole()较长,但是.ToConsole()也是更常见的通用.NET模式,对于不具有C ++背景的人来说可能更有意义,并且Intellisense会选择它,让您只需键入.ToC并按空格键即可完成它。
Craig

4
-1:这不仅是一个非常特殊的用例,通常无法轻易应用,而且甚至没有必要使用预处理器宏的情况。
nuzzolilo 2014年

6
这不是预处理程序宏。该问题的解决方案明确指出:“ C#不支持像c这样的预处理器宏”
anthonybell

12

虽然您不能编写宏,但是在简化示例示例方面,C#6.0现在提供了静态用法。这是马丁·佩尔尼察(Martin Pernica)在他的“中型”文章中提供的示例:

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

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

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


1
您可以使用[ConditionalAttribute("DEBUG")]这些方法达到与#ifs相同的效果吗?
jpmc26


3

幸运的是,C#没有C / C ++样式的预处理器-仅支持条件编译和编译指示(可能还有我无法回忆起的其他事情)。不幸的是,C#没有元编程功能(这实际上可能在某种程度上与您的问题有关)。


5
我要说的是,保持良好风格不是编程语言的工作。C#具有goto,而开发人员则具有不使用它们的智慧。宏也是如此,但是有时候我真的很想拥有它们!
bytecode77

例如:“Dispatcher.Invoke(代表”将是不错的,如你的WPF代码的宏。
bytecode77

@ bytecode77 Gotos在C#,C和C ++中非常有用,可以跳出嵌套循环。像Java的继续/中断标签之类的方法更加智能,但是gotos可以做到这一点。但是,宏在该语言处理多平台支持的语言中没有用。

1
即使我在优点缺点方面都鼓励大家这样做-我看不出goto'ing循环比布尔quit变量优越。嵌套语句到位后,代码将变得难以阅读和维护。PHP有break 2..n;声明,C#没有。但是,C#具有足够的LINQ扩展,实际上在很多情况下不使用嵌套循环,从而使代码以不同的方式可读-我坦诚地说,这是首选。
bytecode77

我们在这里讨论了如何处理热循环的手写配置文件。幸运的是,单词的选择确实很糟糕。
约书亚

1

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


5
这并不能为您提供CPP宏字符串化支持。宏非常有用,因为它们将代码更像是纯文本而不是代码。但是,OP似乎并没有实际需要/需要宏。对于他的目的,这将是一个完全有效(且更好)的解决方案。
binki 2014年

1

我建议您编写扩展名,如下所示。

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

希望这会有所帮助(并且不要太晚:))


5
对我来说,这只是令人困惑
LuckyLikey 2015年

2
有时我称之为延伸性炎。为我建立一个API DLL通常会导致越来越多的扩展而不是方法。例如ValidateUrl(this string)-到目前为止,我在课堂上更喜欢这种东西,因为这两种this object方法都会使智能感知(尤其是)肿,并且有时使找到此类方法变得晦涩。拥有Validate.Url(string)不会破坏代码,并且显然很容易被他人发现和利用。
bytecode77

我同意你的
观点

0

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.