代码之家  ›  专栏  ›  技术社区  ›  Brad Leach

C预处理器

  •  20
  • Brad Leach  · 技术社区  · 16 年前

    虽然C语言规范确实包含预处理器和基本指令(γ定义、αIF等),但语言没有在C/C++等语言中发现的相同的灵活预处理器。我认为缺乏如此灵活的预处理器是安德斯·海斯伯格(Anders Hejlsberg)做出的设计决策(不过,不幸的是,我现在找不到这方面的参考)。从经验来看,这确实是个不错的决定,因为在我做大量C/C++的时候,有一些很难维护的宏创建回来了。

    也就是说,在很多情况下,我可以找到一个稍微更灵活的预处理器。一些简单的预处理器指令可以改进如下代码:

    public string MyProperty
    {
      get { return _myProperty; }
      set
      {
        if (value != _myProperty)
        {
          _myProperty = value;
          NotifyPropertyChanged("MyProperty");
          // This line above could be improved by replacing the literal string with
          // a pre-processor directive like "#Property", which could be translated
          // to the string value "MyProperty" This new notify call would be as follows:
          // NotifyPropertyChanged(#Property);
        }
      }
    }
    

    写一个预处理器来处理这样非常简单的情况是个好主意吗?史蒂夫·麦康奈尔在信中写道 Code Complete (P208):

    编写自己的预处理器 如果一种语言不包含预处理器,那么编写一个预处理器就相当容易了…

    我被撕裂了。这是一个设计决定,让这样一个灵活的预处理器离开C。然而,我高度尊敬的一位作者提到,在某些情况下,这可能是可以的。

    我应该建立一个C预处理器吗?有没有一个可以做我想做的简单的事情?

    11 回复  |  直到 16 年前
        1
  •  11
  •   Matt Hamilton    16 年前

    考虑一下面向方面的解决方案,比如 PostSharp ,根据自定义属性在事实之后插入代码。它与预编译程序相反,但可以为您提供所需的功能(属性更改通知等)。

        2
  •  6
  •   John Millikin    16 年前

    我应该建立一个C预处理器吗?有没有一个可以做我想做的简单的事情?

    您总是可以使用C预处理器——C足够接近,语法方面。M4也是一种选择。

        3
  •  4
  •   Jorge Córdoba    16 年前

    我知道很多人认为短代码等于优雅的代码,但事实并非如此。

    您提出的示例在代码中得到了完美的解决,正如您所展示的那样,您需要一个预处理器指令来做什么?您不想“预处理”您的代码,您希望编译器在您的属性中为您插入一些代码。这是普通代码,但这不是预处理器的目的。

    以你的例子,你把极限放在哪里?很明显,这满足了一个观察者模式,毫无疑问这将是有用的,但是有很多事情是有用的,因为代码提供了 灵活性 其中预处理器没有。如果您试图通过预处理器指令实现通用模式,那么您将以一个预处理器结束,该预处理器需要与语言本身一样强大。如果你想 使用预处理器指令以不同的方式处理代码 但是如果你只是想要一个代码片段,那就另辟蹊径吧,因为预处理器不是故意这样做的。

        4
  •  3
  •   chtenb    8 年前

    不为C构建预处理器的主要论点是在Visual Studio中集成:要使IntelliSense和新的后台编译无缝工作,需要付出很多努力(如果可能的话)。

    替代方法是使用Visual Studio生产力插件,如 ReSharper CodeRush . 据我所知,后者有一个无与伦比的模板系统,并且具有出色的 refactoring 工具。

    另一件有助于解决您所指问题的确切类型的事情是AOP框架,例如 PostSharp .
    然后可以使用自定义属性添加通用功能。

        5
  •  2
  •   user2831877    11 年前

    使用C++风格的预处理器,可以将OP的代码简化为这一行:

     OBSERVABLE_PROPERTY(string, MyProperty)
    

    可观察的属性大致如下:

    #define OBSERVABLE_PROPERTY(propType, propName) \
    private propType _##propName; \
    public propType propName \
    { \
      get { return _##propName; } \
      set \
      { \
        if (value != _##propName) \
        { \
          _##propName = value; \
          NotifyPropertyChanged(#propName); \
        } \
      } \
    }
    

    如果你有100个属性需要处理,那大约是1200行代码,而不是100行。哪个更容易阅读和理解?哪个更容易写?

    使用C,假设您剪切和粘贴以创建每个属性,即每个属性8次粘贴,总共800次。使用宏时,根本不粘贴。哪个更可能包含编码错误?如果您必须添加ISdirty标志,哪一个更容易更改?

    当大量情况下可能存在自定义变体时,宏没有那么有用。

    与任何工具一样,宏也可能被滥用,甚至在错误的手中也可能是危险的。对于一些程序员来说,这是一个宗教问题,一种方法相对于另一种方法的优点是不相关的;如果是您,则应避免使用宏。对于我们这些经常、熟练、安全地使用非常锋利的工具的人来说,宏不仅可以在编码时立即提高生产率,还可以在调试和维护期间提供下游的生产率。

        6
  •  1
  •   Timbo    16 年前

    要获取当前执行的方法的名称,可以查看堆栈跟踪:

    public static string GetNameOfCurrentMethod()
    {
        // Skip 1 frame (this method call)
        var trace = new System.Diagnostics.StackTrace( 1 );
        var frame = trace.GetFrame( 0 );
        return frame.GetMethod().Name;
    }
    

    当您在属性集方法中时,名称是set_property。

    使用相同的技术,您还可以查询源文件和行/列信息。

    但是,我没有对其进行基准测试,为每个属性集创建一次StackTrace对象可能太耗时了。

        7
  •  1
  •   Brett Ryan    15 年前

    我认为在实现inotifyPropertiesChanged时,您可能遗漏了问题的一个重要部分。您的消费者需要一种确定物业名称的方法。因此,应该将属性名定义为常量或静态只读字符串,这样使用者就不必“猜测”属性名。如果使用预处理器,使用者如何知道属性的字符串名称?

    public static string MyPropertyPropertyName
    public string MyProperty {
        get { return _myProperty; }
        set {
            if (!String.Equals(value, _myProperty)) {
                _myProperty = value;
                NotifyPropertyChanged(MyPropertyPropertyName);
            }
        }
    }
    
    // in the consumer.
    private void MyPropertyChangedHandler(object sender,
                                          PropertyChangedEventArgs args) {
        switch (e.PropertyName) {
            case MyClass.MyPropertyPropertyName:
                // Handle property change.
                break;
        }
    }
    
        8
  •  0
  •   billpg    16 年前

    如果我设计下一个版本的C,我会想到每个函数都有一个自动包含的局部变量,其中包含类名和函数名。在大多数情况下,编译器的优化器会将其取出。

    不过,我不确定这类东西有多大需求。

        9
  •  0
  •   Brad Leach    16 年前

    @Jorge写道:如果你想用不同的方式处理你的代码,可以使用预处理器指令,但是如果你只是想要一个代码片段,那么就另辟蹊径,因为预处理器并不打算这样做。

    有趣。我并不认为预处理器必须这样工作。在提供的示例中,我正在执行一个简单的文本替换,它与上的预处理器定义一致。 Wikipedia .

    如果这不是预处理器的正确使用,我们应该称之为简单的文本替换(通常需要在编译之前发生)吗?

        10
  •  0
  •   Marcus Hansson    11 年前

    至少对于提供的场景,有一个比构建预处理器更干净、类型安全的解决方案:

    使用泛型。像这样:

    public static class ObjectExtensions 
    {
        public static string PropertyName<TModel, TProperty>( this TModel @this, Expression<Func<TModel, TProperty>> expr )
        {
            Type source = typeof(TModel);
            MemberExpression member = expr.Body as MemberExpression;
    
            if (member == null)
                throw new ArgumentException(String.Format(
                    "Expression '{0}' refers to a method, not a property",
                    expr.ToString( )));
    
            PropertyInfo property = member.Member as PropertyInfo;
    
            if (property == null)
                throw new ArgumentException(String.Format(
                    "Expression '{0}' refers to a field, not a property",
                    expr.ToString( )));
    
            if (source != property.ReflectedType ||
                !source.IsSubclassOf(property.ReflectedType) ||
                !property.ReflectedType.IsAssignableFrom(source))
                throw new ArgumentException(String.Format(
                    "Expression '{0}' refers to a property that is not a member of type '{1}'.",
                    expr.ToString( ),
                    source));
    
            return property.Name;
        }
    }
    

    这可以很容易地扩展为返回 PropertyInfo 相反,允许你得到更多的东西,而不仅仅是财产的名称。

    因为它是一个 Extension method ,您几乎可以对每个对象使用此方法。


    而且,这是类型安全的。
    压力太大了。

    (我知道这是个老问题,但我发现它缺乏实际的解决办法。)

        11
  •  -1
  •   Community pid    7 年前

    如果您准备放弃C您可能需要查看 Boo 具有难以置信的灵活性的语言 macro 通过支持 AST (抽象语法树)操作。如果你能抛弃C语言,那真的是一件很棒的事情。

    有关boo的更多信息,请参阅以下相关问题: