虽然 C# 规范确实包含预处理器和基本指令(#define、#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);
    }
  }
}

编写一个预处理器来处理像这样的极其简单的情况是一个好主意吗?史蒂夫·麦康奈尔写道 代码完成 (第208页):

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

我很伤心。将如此灵活的预处理器从 C# 中剔除是一个设计决定。然而,我非常尊敬的一位作者提到,在某些情况下这可能没问题。

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

有帮助吗?

解决方案

考虑看看面向方面的解决方案,例如 后锐利, ,它根据自定义属性事后注入代码。它与预编译器相反,但可以为您提供您正在寻找的功能(PropertyChanged 通知等)。

其他提示

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

您始终可以使用 C 预处理器——C# 在语法方面足够接近。M4也是一个选择。

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

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

以你的例子来说,你把限制放在哪里?显然,这满足了观察者模式,并且毫无疑问这将是有用的,但是有很多有用的事情实际上已经完成了,因为代码提供了 灵活性 而预处理器则没有。如果您尝试通过预处理器指令实现常见模式,您将得到一个需要与语言本身一样强大的预处理器。如果你想 使用预处理器指令以不同的方式处理代码 但如果您只想要一个代码片段,那么请寻找另一种方法,因为预处理器并不打算这样做。

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

 OBSERVABLE_PROPERTY(string, MyProperty)

OBSERVABLE_PROPERTY 看起来或多或少像这样:

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

如果您有 100 个属性需要处理,则大约需要 1,200 行代码与 1,200 行代码。〜100。哪个更容易阅读和理解?哪个更容易写?

对于 C#,假设您通过剪切和粘贴来创建每个属性,则每个属性需要粘贴 8 次,总共 800 次。有了宏,根本就不用粘贴了。哪个更有可能包含编码错误?如果您必须添加例如,则更容易更改IsDirty 标志?

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

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

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

替代方法是使用 Visual Studio 生产力插件,例如 锐锐 或者 代码冲刺。据我所知,后者拥有无与伦比的模板系统,并且具有出色的功能 重构 工具。

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

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

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。

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

但是,我没有对此进行基准测试,为每个属性集创建一次堆栈跟踪对象可能是一项非常耗时的操作。

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

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

如果我正在设计 C# 的下一个版本,我会考虑每个函数都有一个自动包含的局部变量,用于保存类的名称和函数的名称。在大多数情况下,编译器的优化器会将其删除。

但我不确定对这类事情的需求有多大。

@豪尔赫写道:如果您想以不同的方式处理代码,请使用预处理器指令,但如果您只想要代码片段,请找到另一种方法,因为预处理器并不打算这样做。

有趣的。我真的不认为预处理器一定以这种方式工作。在提供的示例中,我正在进行简单的文本替换,这与预处理器的定义一致 维基百科.

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

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

使用泛型。就像这样:

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, ,您几乎可以在每个对象上使用此方法。


此外,这是类型安全的。
无论如何强调这一点都不过分。

(我知道这是一个老问题,但我发现它缺乏实用的解决方案。)

如果您准备放弃 C#,您可能需要查看 具有令人难以置信的灵活性的语言 支持通过 谷草转氨酶 (抽象语法树)操作。如果你能放弃 C# 语言,那真是太棒了。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top