为什么C#中没有宏? (注意:这是一个提问标题,不需要回答)

52

第一次学习C#时,我很惊讶他们在宏的支持上没有像C / C ++中那样的能力。我知道C#中存在#define关键字,但与我在C / C ++中所钟爱的相比,它大大缺乏。 有人知道为什么真正的宏在C#中不见了吗?

如果类似问题已经被提出,我向您道歉-我保证我花了足足5分钟的时间寻找重复问题。


27
我可以用三个词回答(加上一个商标):宏很邪恶™。 - Randolpho
2
是的,老实说,在大多数C++代码中,现在很少有使用宏的理由。 - Ed S.
3
那里有很多好的答案,所以这只是一个旁白——C#并非源于C或C++,它源于Java。Java仅基于C/C++但试图消除许多不好的部分(其中宏和整个预处理器可能是最大的不好的部分)。命名似乎会误导人们——还有这种语言添加功能的速度比Java要快得多... - Bill K
14
宏非常棒!它们提供的灵活性真的很棒。当然,它们允许你给自己惹麻烦,但我认为完全抵制它们是一个错误。 - Andrew Garrison
1
@Andrew:这些大多数都根本不适用于C#。在我10多年的行业编码中,C/C++预处理器的“灵活性”是最大的不可读代码来源。C#通过指令做对了:1)严格用于条件编译;2)只允许#define作为文件中的第一项;3)不会将指令“泄漏”到其他文件中。 - Sam Harwell
显示剩余7条评论
11个回答

72

来自 C# 常见问题解答。

http://blogs.msdn.com/CSharpFAQ/archive/2004/03/09/86979.aspx

为什么 C# 不支持 #define 宏?

在 C++ 中,我可以定义一个宏:

#define PRODUCT(x, y, z) x * y * z

然后在代码中使用它:

int a = PRODUCT(3, 2, 1);

C# 不允许你这样做。为什么?

其中一些原因是可读性问题。

C# 的主要设计目标之一是保持代码的可读性。拥有编写宏的能力使程序员能够创建自己的语言——这种语言不一定与底层代码相关。为了理解代码的作用,用户不仅必须了解语言的工作原理,还必须了解当前生效的所有 #define 宏。这使得代码更难以阅读。

在 C# 中,您可以使用方法代替宏,在大多数情况下,JIT 将内联它们,使您具有相同的性能方面。

还有一个比较微妙的问题。宏是以文本形式完成的,这意味着如果我写:

int y = PRODUCT (1 + 2, 3 + 4, 5 + 6)

我期望得到的是 3 * 7 *11 = 231 的结果,但实际上,根据我定义的展开式,结果是:

int y = 1 + 2 * 3 + 4 * 5 + 6;

33这个问题可以通过巧妙地使用括号来解决,但很容易编写一个只在某些情况下起作用的宏。虽然C#严格来说没有预处理器,但它具有条件编译符号可用于影响编译。这些符号可以在代码中定义或通过编译器参数定义。C#中的“预处理”指令(仅因与C/C++一致而命名为此,尽管没有单独的预处理步骤)如下所示:

#define 和 #undef 用于定义和取消定义条件编译符号

#if、#elif、#else 和 #endif

用于有条件地跳过源代码段

#line 用于控制错误和警告发出时的行号。

#error 和 #warning 用于发出错误和警告。

#region 和 #endregion

用于明确标记源代码段。

有关上述内容的更多信息,请参见ECMA规范书第9.5节。还可以使用方法上的Conditional属性实现条件编译,以便当适当的符号被定义时,对该方法的调用将仅被编译。有关此功能的更多信息,请参见ECMA规范书第24.4.2节。

作者:Eric Gunnerson


1
谢谢Hank,我一直在想为什么它看起来不像我复制和粘贴的那样。 - DouglasH
2
我每天都感谢上帝,因为我不必调试大量含有晦涩宏的程序。是否有任何理由使用它们,而简单的良好编程实践就无法胜任呢? - weberc2
4
这是一个问题:WinError.h。如果我想在C#中确定WIN32错误的含义,我必须去找到WinError.h的拷贝,把它修改成包含数千个常量的C#类(或者找到已经修改好的类),并且由于在有预处理器的语言中不需要额外的空间,但现在我的输出程序集大小从45k增加到了180k。此外,如果这段代码将来要在新版本(或其他不同的)Windows上构建,我还必须去找到它们的WinError.h,并进行类似的修改以及类似的代码增加。 - reirab
6
有趣的是,C#禁止使用类似C语言的宏因为它们是邪恶的,但是一个用C语言编写的非常CIL解释器在源代码中使用了它们...真是虚伪。 - Petr
请原谅我的亵渎,但我认为附属设备和宏一样邪恶;你以为只是访问一个字段,于是毫无思考地使用。然后你发现实际上在 getter/setter 中有数十倍的计算量。有时这仅仅通过“良好的实践”来证明。 - mireazma
1
我认为C#的设计者在反射方面违反了可读性规则。XAML绑定可能非常难以“阅读”。 - Sam Hobbs

45

这样你就可以一遍又一遍地快乐地输入“THIS”了。

// Windows presetation foundation dependency property.
public class MyStateControl : ButtonBase
{
  public MyStateControl() : base() { }
  public Boolean State
  {
    get { return (Boolean)this.GetValue(StateProperty); }
    set { this.SetValue(StateProperty, value); } 
  }
  public static readonly DependencyProperty StateProperty = DependencyProperty.Register(
    "State", typeof(Boolean), typeof(MyStateControl),new PropertyMetadata(false));
}

很明显,C#和.NET的设计师从未真正使用过他们创建的库或框架。如果他们这样做了,他们就会意识到一些形式的卫生宏系统肯定是必要的。

不要让C和C++的弱小宏使您对编译时解析代码的强大功能失去信心。编译时解析和代码生成允许您更有效地表达代码的含义和意图,而无需拼写源代码的所有琐碎细节。例如,如果您可以用下面的代码替换上面的代码:

public class MyStateControl : ButtonBase
{
  public MyStateControl() : base() { }

  [DependencyProperty(DefaultValue=true)] 
  bool State { get; set; }
}

Boo 使用它们,OcamML (至少是 Meta ML)也使用它们,C 和 C++ 也使用它们(尽管是以不太友好的形式,但总比根本没有强)。C# 则没有。


6
终于有人不会一味地喊出“宏是邪恶的”这种陈词滥调了。 - StephenG - Help Ukraine

28
C++风格的宏会增加很多复杂性,但没有相应的好处,这是我的经验。在C#或Java中,我当然也不想念它们。(在C#中,我很少使用预处理器符号,但有时很高兴它们存在。)
现在,一些人呼吁采用Lisp风格的宏,我对此知之甚少,但肯定比C++风格的宏更令人愉悦。
你特别想要使用宏来做什么?我们可能能帮助您以更符合C#习惯的方式思考...

3
我讨厌 #define 宏。我非常高兴 C# 不支持这样的宏。实际上,我从来没有见过一个好的 #define 使用案例。 - Bobby Cannon
在C语言中,有很多好的(或至少是必要的)使用#define的方法。在C++中,它们主要用于支持条件编译,任何其他用途可能都不是一个好主意。 - David Thornley
1
宏允许或支持C / C ++中的某些技巧,但它们实际上只是开发人员希望编译器支持当前不支持的内容的标志。 - Blair Davidson
1
@BobbyCannon 在Linux内核、OpenCV等中,有很多好的宏定义用法。例如,在OpenCV中,如果您想添加一个测试用例,则使用宏TEST并为测试定义一个函数。我知道您不喜欢使用宏,但在我看来,这绝对有其优点。 - user1132959
@BlairDavidson:我同意,但这正是宏的关键所在。你不能期望编译器提前支持你可能需要的所有东西。 - Dolda2000
显示剩余5条评论

11

C#的目标受众比C++、C或汇编更广泛(或用其他术语说,是消费者基础)。实现这一目标的唯一方法是吸引技能较差的程序员。因此,所有强大但危险的工具都被取走了。例如,宏,多重继承,控制对象生命周期或类型不透明编程。

同样地,火柴、刀子和钉枪也很有用和必要,但它们必须放在儿童拿不到的地方。(不幸的是,纵火、谋杀、内存泄漏和难以阅读的代码仍然会发生)。

在指责我不懂C#之前,你写了多少次这样的代码:

protected int _PropOne;
public int PropOne
{
    get
    {
        return _PropOne;
    }
    set
    {
        if(value == _PropOne) { return; }
        NotifyPropertyChanging("PropOne");
        _PropOne = value;
        NotifyPropertyChanged("PropOne");
    }
}

使用宏时,这16行的每次出现都会像这样:

DECLARE_PROPERTY(int, PropOne)
DECLARE_PROPERTY(string, PropTwo)
DECLARE_PROPERTY(BitmapImage, PropThree)

似乎你对“熟练”的定义意味着不会犯错误。如果是这样的话,“熟练”的程序员将不会利用高级编程结构,而是使用原始地址而非变量。虽然你提供的代码确实更简单,但它会破坏大多数调试工具。请注意,你的最后一点取决于首先“正确”编写宏。如果我们能够始终编写“正确”的代码,那么我们就不会犯错,也就不需要宏来捕捉错误。至少没有宏,我们可以快速调试。 - weberc2
1
@weberc2 一点也不。在我的字典中,“熟练”意味着“能够编写可读的代码”,包括无错误的宏以及知道何时使用它们和何时不使用它们。编写宏的“技巧”是编写代码,调试它,然后将其提取到宏中。宏用于提取100%可靠且无需调试的代码。我向您保证,在调试时,宏不是问题,除非特别针对使代码难以阅读。而且你错了:在字符串NotifyPropertyChanging中调试错误的属性名称是困难的。这是一个真实的例子。 - Agent_L
@weberc2 看起来我们只是以不同的方式计算成本。如果你知道任何简化NofityPropertyChanged的方法(除了VS代码生成模板),请告诉我,因为这种开销正在折磨我和我一起工作的所有人。很抱歉,但是你关于静态方法的建议是无意义的。问题在于将包含属性名称的字符串与属性本身的名称绑定在一起。据我所知,这是不可能的,除非使用宏。 - Agent_L
2
@weberc2 抱歉,伙计,但你刚刚证明了自己是反宏定义迷的人,即使被它打在脸上也否认其价值。我无法与声称调试宏比调试字符串更难的人争论 - 就我所知,这根本不可能。 - Agent_L
1
@weberc2 我在谈论INotifyPropertyChanged接口及其PropertyChangedEventArgs,由Microsoft定义PropertyName为字符串。这是绝对基础,在使用Forms(或Linq2SQL)编写动态用户界面的任何应用程序中都需要。既然您不熟悉它,那么为什么要参与关于您一无所知的主题的讨论呢?您不知道的是,大多数应用程序中有数百个这样的块,而“PropNameOne”中最轻微的错误会产生难以捉摸且绝对无法调试的问题。 - Agent_L
显示剩余7条评论

6

C/C++中的宏常量被用来定义常量、生成小型内联函数以及与编译代码直接相关的各种事情(如#ifdef)。

在C#中,您有强类型常量,足够智能的编译器可以在必要时内联函数,并且知道如何以正确的方式编译代码(没有预编译头文件的荒谬)。

但是,如果您真的想这样做的话,也没有特别的理由不先通过C预处理器运行您的CS文件 :)


3
好的观点。如果你真的想要,没有什么能阻止使用C预处理器。 - Matthew Lock

2
作为一名长期使用C#的程序员,我曾经去学习了一段时间的C++,现在我想念C#丰富的元编程支持。至少,我现在更加深刻地理解了元编程的意义。
我真的很希望看到C#中像Nemerle中那样的宏支持。它似乎为语言添加了一种非常自然和强大的扩展能力。如果你还没有看过它,我真的建议你这样做。
Wikipedia上有一些很棒的例子。

1

宏在 C++ 中被过度使用,但它们仍然有用处,然而在 C# 中,由于反射和更好地集成使用异常进行错误报告,大多数这些用途都不相关。


1

0

任何认同宏定义不好的观点的人都应该读一下《双手合十》这本书。http://en.wikipedia.org/wiki/With_Folded_Hands 这本书讲述了一个故事,告诉我们如何防止人们做傻事,甚至到了防止他们做明智之举的地步。

虽然我喜欢C#,但我真的很讨厌它对实际软件工程师的愚蠢化贡献。所以,是的,把宏定义留给专业人士吧。顺便说一下,变量的命名也应该留给专业人士。否则会导致代码难以阅读。为了遵循“代码必须最终可读”的完整声明,所有变量都应该按A-Z、a-z(或其他任意构造,比如只使用名词)进行命名。因为有些不熟练的人可能会将其变量命名为“SomethingUsefulButNotAllowedByTheCompilerBecauseSomeUsersMayDoDumbThings”。


0

宏是一个工具,用于那些大多数程序员比编译器聪明的日子。在C/C++中,仍然有一些情况是正确的。

如今,大多数程序员都没有C#编译器/运行时聪明。


7
编译器变得更加智能了,还是程序员变得更加愚蠢了? - Jeff Atwood
两者都是如此。"智能"编译器会让开发人员变得愚蠢。虽然C#是一种易学的语言,但对于本地P/INVOKE库的内存管理却像是地狱... - Петър Петров

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