C#中的高级条件编译

时间:2018-02-02 23:20:37

标签: c# conditional-compilation

我正在探索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,并创建实现特定行为的具体版本,例如WidgetForTarget1WidgetForTarget2等。听起来不错,但具体的小部件不能继承类,只能继承接口。有时它从根本上说是不可接受的。如果我提取接口IWidget,那么具体的实现将包含大量的复制 - 粘贴 - 它很糟糕且难以维护,很好的尝试。

我开始关注partial类/方法,希望有一个解决方法,但没有。有一种丑陋的方式:在部分类的不同文件中提取依赖于编译的代码并包装整个文件#if/#endif,但这是我试图摆脱的丑陋,该死的!

我考虑过编译模块(.netmodule),但实际上是它的程序集,除了没有清单。

我正在考虑使用发布商政策等装配绑定(重定向版本),但不是这样,无论如何,这是政治家用于其他目的,此外,它仍然是难以维持。

总之,我很绝望,并且拒绝相信这种工作没有好办法。值得注意的是,在C ++中,这很简单:我可以在头文件中声明该类,然后在一个.cpp文件中实现一般行为,并在其他cpp文件中实现与配置相关的行为并应用这些.cpp用于编译的文件,具体取决于我的设置解决方案。

2 个答案:

答案 0 :(得分:1)

根据构建目标具有不同实现的接口(甚至基类)可以工作,并且在概念上类似于C / C ++中的头/实现方法。我不确定是否可以拥有不同的源文件,具体取决于Visual Studio中项目的构建目标 - 可能需要维护不同的项目。但这仍然是一种改进。

将接口与实现分开是一种常用的设计模式,便于测试。

还可以综合构建VS解决方案和项目,例如:使用cmake,或者直接使用make,可能作为VS中的自定义构建。

答案 1 :(得分:-1)

  

如果/#ENDIF

必须死。如果你没有很多这样的话,可以通过静态初始化或在构造函数中进行检查。