摆脱 C# 中的预编译器指令
Posted
技术标签:
【中文标题】摆脱 C# 中的预编译器指令【英文标题】:Getting rid of precompiler directives in C# 【发布时间】:2011-07-18 05:55:58 【问题描述】:我被要求维护一些不像我想要的那样遗留的代码,并且它充斥着编译器指令,使其几乎不可读并且几乎可以维护。举个例子:
#if CONDITION_1
protected override void BeforeAdd(LogEntity entity)
#else
protected override void BeforeAdd(AbstractBusinessEntity entity)
#endif
#if CONDITON_1
entity.DateTimeInsert = DateTime.Now;
#else
((LogEntity) entity).DateTimeInsert = DateTime.Now;
#endif
base.BeforeAdd(entity);
using
指令更漂亮:
#if CONDITION_1
using CompanyName.Configuration;
#endif
#if CONDITION_2||CONDITION_1
using CompanyName.Data;
using CompanyName.Data.SqlBuilders;
#else
using CompanyName.Legacy.Database;
using CompanyName.Legacy.Database.SQLBuilders;
using CompanyName.Legacy.Database.SQLBuilders.parameterTypes;
#endif
我想我会尝试一下ConditionalAttribute
,但在这种情况下不太可行
有什么办法可以摆脱这个编译器指令的噩梦吗?
代码是针对.NET 3.5
编译的。
更新:
Oded 回答建议删除 BeforeAdd
方法周围的编译器指令,从而使其重载。不幸的是,这不起作用,因为这两种方法都应该覆盖 AbstractBusiness
类,该类根据最终包含的程序集提供两种不同的实现:
protected virtual void BeforeAdd(TEntity entity)
或
protected virtual void BeforeAdd(AbstractBusinessEntity entity)
此代码从公司过去一段时间创建的一组库中获取其依赖项,并且从那时起一直在“升级”。他们现在拥有这组库的 4 个不同版本,具有冲突的命名空间和不同的实现。全部以与使用(非常)旧版本的应用程序的“向后兼容性”的名义。
结论
我最终选择了@Oded 的答案,因为它作为一种通用方法最有意义(K.I.S.S. 等等)。不过在这种情况下我不能使用它;你在这里看到的只是冰山一角。我不想亲吻。这个代码,如果它付钱给我。
【问题讨论】:
我敢打赌,写这篇文章的人认为他们非常聪明...... 如果它还活着,我会告诉你在疾病杀死它接触到的任何东西之前把它拿出来并射杀它。 为什么会有两种不同的方法?在什么场景下使用? @Lasse - 它们位于公司开发的一组库的两个不同版本中,因此根据调用应用程序编译的库版本来使用它们。删除其中任何一个都会破坏公司其他地方运行的多个应用程序。 您确定不能使用 DVCS 或类似的分支来处理这个问题? IE。为common创建一个分支,为type 1创建一个分支,为type 2创建一个分支,将代码添加到common并在common时合并到两个分支中,而不是在一个或另一个分支中开发? 【参考方案1】:在第一种情况下,看起来您可以简单地使用该方法的多个重载而不是此构造。重载解决方案应该在这一点上解决问题。
在第二种情况下(使用指令) - 您可以为某些指令设置别名并包含所有指令,在需要时使用别名。当包含所有命名空间时会发生什么?有名称冲突吗?
【讨论】:
@Oded - 感谢您的提示。不幸的是,是的,大量的命名空间和类名冲突。 @Sergi - 那么,在不同的命名空间中定义了相同的类名? 更糟糕的是:看到这两种方法都是覆盖?这使得无法使用重载,因为如果不满足CONDITON_1
,则采用 LogEntity
的方法没有有效的覆盖
@Oded - 在某些情况下是的。
@Sergi - 我能建议的就是开始清理你能做的事情。您应该能够慢慢删除预处理器指令,并且随着您的进行,事情应该会变得更容易。在需要的地方重命名函数,创建包装器重载......真的。【参考方案2】:
根据我所见,最初的开发人员似乎没有任何继承和多态意识。从代码中很难分辨,但似乎 LogEntity 和 AbstractBusinessEntity 具有共同的属性。是否有继承模型或者它们是两个完全不相关的类?如果它们不相关,您可以创建一个继承模型或一个它们都可以实现的接口吗?如果您粘贴课程可能会有所帮助。
长话短说,我不会浪费时间尝试使用当前形式的代码。我会不惜一切代价找到一种消除编译器指令的方法。它看起来并非完全无法挽救,但可能需要一些努力。
【讨论】:
【参考方案3】:我不知道这是否实用,但我会在我的 DVCS Mercurial 中创建分支来处理这个问题。
在我修复错误/添加常见代码时,我将有 2 个分支在运行,而第 3 个分支暂时可用。
以下是我创建初始版本的方法:
5---6---7 <-- type 1 of library
/
1---2---3---4
\
8---9--10 <-- type 2 of library
仅修复其中一个中的错误:
5---6---7--11 <-- bugfix or change only to type 1
/
1---2---3---4
\
8---9--10
修复常见的错误:
5---6---7--11--13--15 <-- merged into type 1
/ /
1---2---3---4--11--12---+-------+ <-- common fix(es)
\ \
8---9--10--14 <-- merged into type 2
注意:这假设您不会在类型或公共分支中进行严厉的重构,如果您这样做,您可能会更好地应对当前的情况,至少与这样的分支方式相比。任何这样的重构都会让未来的合并真的很痛苦。
【讨论】:
3 个字:Team Foundation Server。 ;) 说实话我不是特别喜欢 TFS。它经常对自己的合并感到困惑。 IE。问它分支X的哪些变化不在分支Y,它列出了很多。然后你将其中的一些合并到分支 Y 中。然后你反过来问它,它会愉快地列出你刚刚提交的合并变更集,因为它不存在于 X 中。我会在一周中的任何一天使用 Mercurial 作为只要我有选择:) 男孩,我同意。我只是说在这种情况下这不是一个选择。该公司使用 TFS,这就是它的结束...... 啊哈,我以为你的意思是你推荐的 :) 您好,downvoter,请注意,我不介意您对我的问题和/或答案投反对票。我完全理解并感谢人们有不同的意见和观点,但如果您能发表简短评论,说明您为什么对我投反对票,那将是非常受欢迎的。我是不是输入了一些不准确的东西?什么是完全错误的?你只是不同意吗?不管它是什么,即使您只是启动一个 sockpuppet 帐户来发表匿名评论,我也会欢迎它。我想改善我的职位和职业生涯,我总是想学习新的东西,请给我一个机会,好吗?【参考方案4】:我会声称问题不在这堂课上。这个类只是一个症状。问题出在调用 BeforeAdd 的基类中。如果你可以在那里重构,那么你就不需要条件编译。
如果您有冲突的名称和命名空间,您可以使用 using 关键字(不是用于程序集的关键字)来解决这个问题。
所以你可以做类似的事情
using LegacyLogEntity = Some.Fully.Qualified.Namespace.LogEntity;
using SomeOtherLogEntity = Some.Other.Fully.Qualified.Namespace.CurrentLogEntity;
// ..
LegacyLogEntity entity = new LegacyLogEntity();
我也认为问题出在基类上,而不是这个类本身。
在这种情况下,您可以通过使用适配或接口来绕过这种废话。
我不知道另一个类叫什么,但假设它被称为 EntityAggregator。
public interface IEntity
DateTime InsertionTime get; set;
然后在您的聚合器基类中:
protected virtual void BeforeAdd(IEntity entity)
// whatever
然后在你的子类中:
protected override void BeforeAdd(IEntity entity)
entity.DateTime = DateTime.Now;
base.BeforeAdd(entity);
现在您可以通过实现该接口将其他对象调整为 IEntity。
当我看到这段代码时,我也觉得你可能使用的是事件而不是这段代码。
现在,如果您谈论的是多用途编译,其中代码在两个不同的条件下在两个不同的地方编译,那么您可以通过使用部分类更优雅地做到这一点。
您将 CONDITION_1 代码隔离成如下内容:
// in file WhateverYourClassIs.condition1.cs
#if !CONDITION_1
#error this file should never be included in a build WITHOUT CONDITION_1 set
#endif
public partial class WhateverYourClassIs
protected override void BeforeAdd(LogEntity entity)
entity.DateTimeInsert = DateTime.Now;
base.BeforeAdd(entity);
// in file WhateverYourClassIs.NotCondition1.cs
#if CONDITION_1
#error this file should never be included in a build WITH CONDITION_1 set
#endif
public partial class WhateverYourClassIs
protected override void BeforeAdd(AbstractBusinessEntity entity)
((LogEntity)entity).DateTimeInsert = DateTime.Now;
base.BeforeAdd(entity);
由于代码重复,我不喜欢这种情况。您可以使用 using 关键字来帮助解决这个问题:
#if CONDITION_1
using MyAbstractBusinessEntity = LogEntity;
#else
using MyAbstractBusinessEntity = AbstractBusinessEntity;
#endif
// ...
protected override void BeforeAdd(MyAbstractBusinessEntity entity)
// in CONDITION_1, the case is a no-op
((LogEntity)entity).DateTimeInsert = DateTime.Now;
base.BeforeAdd(entity);
【讨论】:
以我面前的信息很难判断,但我不得不猜测,这仍然使情况变得过于复杂。虽然我喜欢界面的想法(我在下面提出了类似的方法),但对于工厂来说,这看起来是完美的情况。在再次编写单个编译器指令之前,我会研究 StructureMap(或其他一些 IoC 容器)。以上是关于摆脱 C# 中的预编译器指令的主要内容,如果未能解决你的问题,请参考以下文章