C#预处理器

C#预处理器

C# Preprocessor

尽管C#规范确实包含预处理程序和基本指令(#define,#if等),但该语言没有像C / C ++这样的语言中具有相同的灵活预处理程序。我相信缺少如此灵活的预处理器是Anders Hejlsberg的设计决定(尽管很遗憾,我现在找不到对此的参考)。从经验来看,这当然是一个不错的决定,因为当我做很多C / C ++时,已经创建了一些非常糟糕的,不可维护的宏。

也就是说,在许多情况下,我可以找到稍微灵活一些的预处理器有用。可以通过一些简单的预处理程序指令来改进如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
    }
  }
}

编写一个预处理器来处理这种极其简单的情况是一个好主意吗?史蒂夫·麦康奈尔(Steve McConnell)在《代码完成》(Code Complete,p208)中写道:

Write your own preprocessor If a language doesn't include a preprocessor, it's fairly easy to write one...

我被撕裂了。将这样的灵活预处理器排除在C#之外是一项设计决定。但是,我高度尊重的一位作者提到在某些情况下可能还可以。

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


考虑看一下诸如PostSharp之类的面向方面的解决方案,该解决方案基于事实基于自定义属性注入代码。它与预编译器相反,但是可以为您提供所需的功能(PropertyChanged通知等)。


Should I build a C# pre-processor? Is there one available that does the simple things I want to do?

您始终可以使用C预处理器-C#就语法而言足够接近。 M4也是一个选择。


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

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

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


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

1
 OBSERVABLE_PROPERTY(string, MyProperty)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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标志?

当在很多情况下可能存在自定义变化时,宏没有那么有用。

像任何工具一样,宏可能会被滥用,甚至在不正确的人手中也可能很危险。对于某些程序员来说,这是一个宗教问题,一种方法优于另一种方法的优点是无关紧要的。如果是这样,则应避免使用宏。对于我们这些经常,熟练且安全地使用极为锋利的工具的人来说,宏不仅可以在编码时立即提高生产率,而且在调试和维护过程中也可以下游提高生产率。


反对为C#构建预处理程序的主要论点是在Visual Studio中进行集成:要想获得智能感知和使新的背景编译无缝地工作,将需要付出很多努力(如果可能的话)。

替代方法是使用Visual Studio生产力插件,例如ReSharper或CodeRush。
就我所知,后者具有无与伦比的模板系统,并带有出色的重构工具。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
    }
}


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

1
2
3
4
5
6
7
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对象可能会非常耗时。


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

使用泛型。像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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,因此几乎可以在每个对象上使用此方法。


此外,这是类型安全的。

不能那么强调。

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


@Jorge wrote: If you want to process your code in a different way the use a preprocessor directive but if you just want a code snippet then find another way because the preprocessor wasn't meant to do that.

有趣。我并不是真的认为预处理器一定会以这种方式工作。在提供的示例中,我正在做一个简单的文本替换,这与Wikipedia上预处理器的定义一致。

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


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

我不确定这种事情有很多需求。


如果您准备放弃C#,则可能想看看Boo语言,该语言通过AST(抽象语法树)操作具有非常灵活的宏支持。如果您可以放弃C#语言,那就真的很棒。

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

  • 非C ++语言的生成式编程?
  • https://stackoverflow.com/questions/595593/who-is-using-boo-programming-language
  • Boo与IronPython
  • NET的良好动态编程语言推荐
  • Boo可以为您做什么?


推荐阅读

    linux指令中的命令?

    linux指令中的命令?,系统,基础,工作,地址,命令,工具,管理,信息,网络,控制台,L

    linux改语言命令行?

    linux改语言命令行?,系统,环境,工具,密码,概念,地方,软件,通信,管理,国际,lin

    linux命令行c语言?

    linux命令行c语言?,代码,系统,工具,环境,工作,保险,发行,命令,文件,终端,linu

    linux命令行指令大全?

    linux命令行指令大全?,工作,地址,系统,信息,命令,目录,工具,管理,基础,控制

    linux编写c语言命令?

    linux编写c语言命令?,系统,基础,环境,代码,盘面,保险,百度,情况,数据,工具,在

    linux退出指令命令?

    linux退出指令命令?,档案,状态,时间,命令,系统,编辑,文件,模式,指令,内容,lin

    linux维护网络命令?

    linux维护网络命令?,网络,地址,系统,工具,信息,服务,电脑,初级,设备,命令,lin

    linux命令下ll指令?

    linux命令下ll指令?,系统,信息,时间,标准,命令,位置,状态,单位,文件,别名,lin

    linux服务器命令指令?

    linux服务器命令指令?,系统,信息,工作,情况,标准,设备,对比,命令,状态,平均,1

    linux查看指令命令?

    linux查看指令命令?,工作,地址,系统,网络,信息,命令,标准,中心,管理,名称,lin

    linux常用命令行指令?

    linux常用命令行指令?,工作,地址,系统,信息,管理,命令,目录,标准,控制台,文

    linux改变语言命令?

    linux改变语言命令?,系统,管理,网上,官方网站,情况,服务,中文,语言,命令,终

    c语言编译linux命令?

    c语言编译linux命令?,代码,工具,环境,系统,基础,保险,百度,语言,源程序,文件

    linux常用命令语言?

    linux常用命令语言?,工作,地址,系统,信息,命令,目录,标准,管理,工具,服务,lin

    r语言命令行写linux?

    r语言命令行写linux?,环境,数据,系统,工具,简介,官网,语言,报告,软件,发展,如

    linux语言查找命令行?

    linux语言查找命令行?,系统,工作,位置,标准,地址,信息,命令,管理,时间,文件,

    python代码的规范建议

    python代码的规范建议,代码,异常,二元,设计,数字,下降,一致,培训,标准,空行