为什么C#中没有宏?

时间:2009-09-02 19:45:34

标签: c# .net macros c-preprocessor

第一次学习C#时,我惊讶于他们不支持与C / C ++相同容量的宏。我意识到#define关键字存在于C#中,但与我在C / C ++中的喜爱相比,它非常缺乏。有谁知道为什么C#中缺少真正的宏?

如果已经以某种形式提出这个问题,我道歉 - 我保证在发布之前花了5分钟时间查找重复项。

11 个答案:

答案 0 :(得分:67)

来自C#faq。

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 ++的一致性而命名,尽管没有单独的预处理步骤)是(来自ECMA规范的文本):

     

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

     

#if, #elif, #else and #endif

     

用于有条件地跳过源代码部分

     

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

     

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

     

#region and #endregion

     

用于显式标记源代码的各个部分。

     

有关上述内容的更多信息,请参阅ECMA规范的第9.5节。也可以使用方法上的Conditional属性来实现条件编译,以便只有在定义了适当的符号时才会编译对方法的调用。有关详细信息,请参阅ECMA规范的第24.4.2节。

     

作者:Eric Gunnerson

答案 1 :(得分:35)

这样你就可以一遍又一遍地打字。

// 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的设计者实际上从未使用他们创建的任何库或框架。如果他们这样做了,他们就会意识到某种形式的hygenic句法宏观系统肯定是有序的。

不要让C和C ++的蹩脚宏的缺点让你厌倦了编译时解析代码的力量。编译时间分辨率和代码生成使您可以更有效地表达代码的意义和意图,而无需详细说明源代码的所有细节。例如,如果您可以用以下内容替换上述内容:

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

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

Boo拥有它们,OcamML(至少Meta ML)拥有它们,C和C ++拥有它们(以一种令人讨厌的形式,但总比没有它们好)。 C#没有。

答案 2 :(得分:28)

根据我的经验,

C ++风格的宏添加了大量的复杂性而没有相应的好处。我当然没有在C#或Java中错过它们。 (我很少在C#中使用预处理器符号,但我偶尔会很高兴他们在那里。)

现在各种各样的人都要求Lisp-style macros,我对此知之甚少,但肯定听起来比C ++风格更令人愉快。

你特别想用宏做什么?我们或许可以帮助您以更加惯用的方式思考C#...

答案 3 :(得分:9)

C#的目标是比C ++,C或ASM更广泛的受众(或其他术语,消费者群体)。实现这一目标的唯一方法是让程序员接受相当不熟练的技能。因此,所有强大但危险的工具都被带走了。即宏,多重继承,对象生存期控制或类型无关的编程。

以同样的方式比赛,刀和钉枪是有用和必要的,但它们必须放在儿童接触不到的地方。 (遗憾的是,纵火,谋杀,内存泄漏和不可读的代码仍然会发生)。

在指责我不考虑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)

答案 4 :(得分:4)

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

在C#中,你有强类型常量,一个足够聪明的编译器,可以在必要时内联函数,并且知道如何以正确的方式编译东西(没有预编译的头文件无意义)。

但是没有特别的理由说明为什么你不能先通过C预处理器运行你的CS文件,如果你真的想要:)

答案 5 :(得分:2)

作为一名长期学习C ++一段时间的C#程序员,我现在错过了对C#元编程的丰富支持。至少,我现在对元编程的含义有了更广泛的认识。

我真的希望看到在C#中Nemerle中灌输的那种宏支持。它似乎为语言添加了非常自然而强大的扩展功能。如果你还没看过,我真的建议这样做。

Wikipedia上有一些很好的例子。

答案 6 :(得分:1)

宏在C++中被过度使用,但它们仍有their uses,但由于反射和错误报告异常的更好集成使用,大多数这些用途与C#无关。

答案 7 :(得分:1)

本文比较了perl和lisp宏,但重点仍然相同:与源级宏(lisp)相比,文本级宏(perl / c ++)会导致大量问题

http://lists.warhead.org.uk/pipermail/iwe/2005-July/000130.html

比我更聪明的人在c#http://www.codeproject.com/KB/recipes/prepro.aspx

中推出了自己的宏系统

答案 8 :(得分:0)

宏是大多数程序员比编译器更聪明的时代的工具。在C / C ++中,仍有一些情况是这样的。

现在,大多数程序员并不像C#编译器/运行时那样聪明。

答案 9 :(得分:0)

任何同意宏都不好的人应该阅读这本书,#34; With Folded Hands。" http://en.wikipedia.org/wiki/With_Folded_Hands它讲述了一个故事,讲述了我们如何能阻止人们做蠢事,直到阻止他们做出非常明智的事情。

虽然我喜欢C#,但我真的很讨厌它会导致实际软件工程师的愚蠢行为。所以,是的,将宏留给专业人士。虽然我们正在努力,但也要将变量命名留给专业人士。这可能会导致一些非常难以理解的代码。要遵循"的完整陈述,代码必须最终可读"所有变量都应命名为A-Z,后跟a-z(或其他任意构造,如名词)。因为一些不熟练的人可能会命名他们的变量" SomethingUsefulButNotAllowedByTheCompilerBecauseTomeUsersMayDoDumbThings"。

答案 10 :(得分:-1)

您可以使用像this

这样的方法对PropertyChanged等宏执行某些操作

如果那比宏更好? 这是一个你必须决定的问题:)