尽管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可以为您做什么?