C#预处理器中的宏定义

81

C#能否像C语言一样使用预处理器语句定义宏吗?我希望简化某些重复语句的输入,例如以下内容:

Console.WriteLine("foo");

33
顺便提一句,对于这种特定的方法,您可以在Visual Studio中键入“cw”,然后按Tab键。 - MasterMastic
2
有一种名为LeMP的Lisp风格宏处理器适用于C#; Lisp风格的宏远比C/C ++宏优越。 但是在新版本的C#中,您可以在源文件顶部添加using static System.Console;后将Console.WriteLine缩短为WriteLine - Qwertie
10个回答

62
不,C#不支持像C语言那样的预处理器宏。另一方面,Visual Studio有代码片段。Visual Studio的代码片段是IDE的一个功能,在编辑器中展开而不是由预处理器在编译时替换代码。

34
不好的程序员不应该为我们其他人破坏优雅的功能。 - Jake
25
宏不是“优雅的功能”,而是对语言设计不足的一种补偿。 - weberc2
89
当同一代码库需要针对多个编译目标(如iOS、Android、MacOSX等)时,宏非常有帮助。缺少宏意味着你必须在#if/#elseif/#else预处理指令之间放置大量代码,而简单的宏可以以高效、优雅和可维护的方式发出每个目标所需的正确代码。简而言之,当你必须包含某些目标上根本无法编译的代码时,C风格的宏非常有用。 - JohnnyLambada
23
这个“更好的解决方案”的问题在于它很繁重,将本来可以用几行简单代码实现的功能分散到多个文件中,必须理解所有文件才能完全理解这个方案。我不认为这是更好的方式。是的,C预处理器被滥用到了令人痛苦的地步(我肯定也是其中之一!),但在许多情况下,它也非常有用,可以让代码变得更简单易懂。 - JohnnyLambada
8
我们在谈论两件不同的事情。我并不是说在大块代码周围加上许多#ifdef是好的,它不是好的。我想说的是,在有限的情况下,嵌入在#ifdef PLATFORM1 #elif PLATFORM2 ...中的#define MACRO(x,y)的审慎使用可以作为工具箱中的一种有用工具。也许你对此持不同意见 -- 在进行了谷歌搜索后,似乎世界上大部分人也持相同意见。但我确实发现了这个珍品。 - JohnnyLambada
显示剩余13条评论

53

你可以使用 C 预处理器(如 mcpp),并将其添加到你的 .csproj 文件中。然后将源文件的 "build action" 从 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>

你可能需要在至少一个文件中手动将Compile更改为Preprocess(在文本编辑器中)-然后“Preprocess”选项应该可以在Visual Studio中选择。

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


22
你需要因为“创新”的解决方案和跳出固有思维得到一些认可。但是对于任何阅读此“解决方案”的人,我要给你一个忠告,请不要这样做。有些事情就是错误的,这是一种很拙劣的方法。这是其中之一。 - danpalmer
16
为什么将 CPP shellout 添加到“MyCommon.targets”会是错误的?有些事情 CPP 可以做,而语言让这些事情变得极其困难。在我看来,开发人员必须在编写“throw new ArgumentNullException("argumentName");”代码时手动将参数名称字符串化,这反而更糟糕。代码合同应该解决了这个问题,但它需要使用“.targets”附加 IL 重写器,附加一个 CPP 调用不应该更糟糕。 - binki
由于这篇文章是2013年的,我想提醒大家C#6的nameof(argumentX)。C# 6和7为之前引起痛苦的琐碎问题添加了许多深刻而直接的解决方案,就像这个问题一样。老实说,我必须同意@danpalmer的观点 - 是的,如果添加了更多这样的黑客,那么任何人都不太可能想要维护一个项目,甚至在几年后编译它。 - bytecode77
1
直到C#实现宏之前,这绝对是最好的解决方案,事实上,是一个不需要思考的完美解决方案。我无法强调这一点。它没有被大多数人使用与其成为“完美”的解决方案无关。顺便说一下,在我看来,这应该成为普遍采用的方案。 - mireazma
1
@mireazma Intellisense停止工作了。所以这并不是一件轻而易举的事情。 - Ayxan Haqverdili
显示剩余2条评论

35

我使用这个代码来避免使用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
10
如果使用tab键补全,就不会很长。你现在正在创建两种做同样事情的方式,这会让其他开发者感到困惑。此外,这并不更加优雅;在Python中或许能接受,但在C#中看起来有些奇怪。 - mpen
4
为创建一个扩展方法点赞。尽管C#不是C++,个人可能会将其称为.ToConsole()而不是Cout()。当然,“Cout”中的“C”代表控制台,而.ToConsole()更长,但.ToConsole()也是更常见的一般.NET模式,对于没有来自C++背景的人来说可能更有意义,而且Intellisense也会捕获它,让您只需键入.ToC并按下空格即可完成它。 - Craig Tullis
4
这不仅是一个非常具体的用例,难以普遍适用,而且甚至不是需要使用预处理器宏的情况。 - marknuzz
7
这不是预处理宏。这道题的解答明确说明:"C#不支持像C语言那样的预处理宏"。 - anthonybell
显示剩余5条评论

14

虽然你不能写宏,但在简化类似于你的示例这样的事情方面,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;
    }
  }
}

这玩意儿虽然不能满足我对宏的需求,但确实非常有用。谢谢! - undefined

9

在C#中没有直接等价于C风格宏的功能,但是使用内联(inline)的静态方法 - 可以带有或不带有#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;
                }
            }
        }

这个作为宏来使用非常完美,但是有一个小缺点:被标记为inline的方法会像其他“普通”方法一样被复制到您程序集的反射部分。


3
你能在这些方法上使用 [ConditionalAttribute("DEBUG")] 来达到与 #if 相同的效果吗? - jpmc26
@jpmc26 请参考 https://dev59.com/fG865IYBdhLWcg3wfeqU。 - Binkan Salaryman

3

幸运的是,C#没有类似于C / C ++的预处理器 - 只支持条件编译和编译指示(以及可能还有其他我无法回忆起的内容)。不幸的是,C#没有元编程能力(这实际上可能与您的问题有些关系)。


9
我认为编程语言的工作不是强制执行良好的风格。C#有goto语句,开发人员有智慧不使用它们。宏也是如此,但有时我真的很想要它们! - bytecode77
在你的WPF代码中,将“Dispatcher.Invoke(delegate”作为宏会很方便。 - bytecode77
@bytecode77 在C#,C和C ++中使用goto可以跳出嵌套循环,这是非常有用的。像Java的continue / break到标签的方法更加智能,但是对于此类情况,goto也可以胜任。然而,在一个由语言处理多平台支持的语言中,宏并不是很有用。 - Winter
1
尽管我鼓励对任何事情都有正反两面的看法,但我不认为通过goto跳出循环比布尔型“quit”变量更优越。一旦嵌套语句出现,代码就变得难以阅读和维护。PHP有“break 2..n;”语句,而C#没有。然而,C#有足够的LINQ扩展,在许多情况下不需要使用嵌套循环,从而使代码以一种不同的方式可读性更好 - 这是我真诚地喜欢的。 - bytecode77
我们在这里讨论如何处理热循环的手写分析。幸运地是一个非常不恰当的词选择。 - Joshua
不用宏!哇!如果我想要进行简单的转换并将其分配给变量,例如#define CONVERT_AMPS_TO_ADC(AMPS) (Uint16)(AMPS*(6.0/2047.0)/0.1),那么在定义变量时就可以这样做。Uint16 u16CurrentLimit_ADC = CONVERT_AMPS_TO_ADC(62.5)。这难道不比Uint16 u16CurrentLimit_ADC = (Uint16)(62.2*(6.0/2047.0)/0.1)更容易理解吗?这可能需要在代码中的许多地方完成。这里有太多例子了。宏可以使代码更简单、更易读,并帮助防止打字错误。 - user1164199

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
3
有时我称之为“扩展症”。为我构建API DLL通常会导致越来越多的扩展而不是方法。例如,像ValidateUrl(this string)这样的东西,我现在更喜欢用类来实现,因为这往往会使智能感知变得臃肿(特别是使用this object),有时也会使查找此类方法变得模糊。使用Validate.Url(string)不会让代码膨胀,显然很容易让其他人发现和利用。 - bytecode77
我同意你的观点。 - Milan Jaric

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

8
这并不能为您提供CPP宏字符串化支持。宏很有用,因为它们将代码视为纯文本而不是代码。然而,问问题的人似乎并不真正需要宏。对于他的目的,这将是一个完全有效(并且更好)的解决方案。 - binki

0

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


1
不是一回事 - 本地函数实际上在你看到的背后创建了普通函数,自动带有许多参数。 - Aristos

-1
使用lambda表达式
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");

这在本质上和C/C++的宏是完全不同的。在C/C++中,宏是一种替换。当C/C++预处理器检测到宏时,预处理器会用你的代码替换掉这个宏。因此,你可以有像返回值、继续等等的东西。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接