我正在探索C#中高级条件编译的可能性。我工作的情况是我需要根据项目的配置有不同的行为。我并不是指平庸DEBUG
/ RELEASE
。
例如,这是我的代码:
namespace Common
{
public struct ActionResult
{
int id;
string summary;
}
public class Widget
{
public ActionResult Process(out string path)
{
// A few lines of code
path = "path_to_file";
var result = DoAction(path);
// A few lines of code
return result;
}
protected virtual ActionResult DoAction(string path)
{
#if RELEASE_TARGET_1
HelperForTarget_1();
#elif RELEASE_TARGET_2
// Do other action...
#endif
#if TRACE_TO_CONSOLE
return new ConsoleLogger(new ActionResult());
#else
return new ActionResult();
#endif
}
[Conditional("RELEASE_TARGET_1")]
private void HelperForTarget_1()
{
// Initializing a pair or triple of class fields.
}
}
}
首先,#if/#endif
看起来非常难看。每个人都知道自动重命名在排除区域不起作用(我知道ReSharper可以,但仍然)。代码看起来像是从碎片缝制的皮革面。很难察觉并难以获得支持;
其次,ConditionalAttribute
不能始终应用于方法。例如,方法可以返回值或具有out参数。一种选择是将DoAction
拆分为一堆小方法,并仍然将ConditionalAttribute
应用于它们。但是,为了补偿返回值和输出参数,您必须使用仅为多个实现之一所需的字段使类复杂化。字段可能必须包裹#if/#endif
,WTF!
WidgetBase
,并创建实现特定行为的具体版本,例如WidgetForTarget1
,WidgetForTarget2
等。听起来不错,但具体的小部件不能继承类,只能继承接口。有时它从根本上说是不可接受的。如果我提取接口IWidget
,那么具体的实现将包含大量的复制 - 粘贴 - 它很糟糕且难以维护,很好的尝试。
我开始关注partial
类/方法,希望有一个解决方法,但没有。有一种丑陋的方式:在部分类的不同文件中提取依赖于编译的代码并包装整个文件#if/#endif
,但这是我试图摆脱的丑陋,该死的!
我考虑过编译模块(.netmodule
),但实际上是它的程序集,除了没有清单。
我正在考虑使用发布商政策等装配绑定(重定向版本),但不是这样,无论如何,这是政治家用于其他目的,此外,它仍然是难以维持。
总之,我很绝望,并且拒绝相信这种工作没有好办法。值得注意的是,在C ++中,这很简单:我可以在头文件中声明该类,然后在一个.cpp文件中实现一般行为,并在其他cpp文件中实现与配置相关的行为并应用这些.cpp用于编译的文件,具体取决于我的设置解决方案。
答案 0 :(得分:1)
根据构建目标具有不同实现的接口(甚至基类)可以工作,并且在概念上类似于C / C ++中的头/实现方法。我不确定是否可以拥有不同的源文件,具体取决于Visual Studio中项目的构建目标 - 可能需要维护不同的项目。但这仍然是一种改进。
将接口与实现分开是一种常用的设计模式,便于测试。
还可以综合构建VS解决方案和项目,例如:使用cmake,或者直接使用make,可能作为VS中的自定义构建。
答案 1 :(得分:-1)
如果/#ENDIF
必须死。如果你没有很多这样的话,可以通过静态初始化或在构造函数中进行检查。