【问题标题】:Advanced conditional compilation in C#C# 中的高级条件编译
【发布时间】:2018-07-13 12:27:53
【问题描述】:

我正在探索 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 类/方法,希望有解决方法,但没有。有一个丑陋的方法:将依赖编译的代码提取到partial类的不同文件中,然后包装整个文件#if/#endif,但这就是我要摆脱的丑陋,该死!

我想过编译模块 (.netmodule) 但实际上它是汇编,只是没有清单。

我正在考虑使用发布者策略等进行程序集绑定(重定向版本),但无论如何,这并不是将政治家用于其他目的,此外,它仍然难以维护。

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

【问题讨论】:

  • 条件编译并不是你真正可以用来组织项目的东西。正如您已经描述的那样,它不会扩展。为什么不使用static 方法,即程序组织?这将是一个进步。
  • @AluanHaddad,很抱歉,但当您谈论使用 static 方法时,我不太明白您的意思。我在static 方法中看不到出路。我想要您的观点的详细信息,提前谢谢您。
  • 我只是说你应该保持简单。继承是 C# 中的许多组织技术之一。您可以编写检查条件然后采取相应措施的静态方法。方法可以通过委托作为参数传递给其他方法。方法可以基于不相关的类型进行静态调度,形成临时组织,使用扩展方法。并且有很多方法可以将它们结合起来,并将它们与多态性一起使用。另外,不要忘记您可以使用全局变量。一个坏主意?是的。比使用#ifdef 构建代码更好吗?是的。
  • @AluanHaddad,你的建议让我思考。我大致了解您的想法,这种方法与我通常的工作流程不同。感谢您的建议。

标签: c# conditional-compilation


【解决方案1】:

如果/#endif

必须死。通过静态初始化或在对象的构造函数中进行检查(如果您没有很多)。

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 我同意手动项目/解决方案管理提供最大的灵活性(通过 cmake 或直接 MSBuild)。但是,随着项目的结构,团队的工作会变得更加复杂,与人为因素相关的错误概率也会增加。最好使用 IDE 管理建筑物。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-14
    • 2010-12-30
    • 1970-01-01
    • 1970-01-01
    • 2014-11-16
    • 1970-01-01
    • 2016-12-21
    相关资源
    最近更新 更多