【问题标题】:Conditional compilation based on Class Library version in C#C#中基于类库版本的条件编译
【发布时间】:2014-05-10 19:53:53
【问题描述】:

在经典 C 中,我可能有一个 1.0 版本的库,它在其 .h 文件中定义了一个常量,例如:

 #define LIBRARY_API_VERSION_1_0

我可以在我的应用程序代码中做这样的事情:

#include "LibraryApi.h"
// ...
int success; 
#ifdef LIBRARY_API_VERSION_1_0
    int param = 42;
    success = UseThisMethodSignature(42);
#endif
#ifdef LIBRARY_API_VERSION_2_0
    float param = 42.0f;
    success = UseOtherMethodSignature(param);
#endif

现在我正在使用 C#。因此,显然#defines 仅适用于定义它们的文件,因此我研究了使用带有常量的静态类的解决方案described here。但是,该解决方案需要在运行时进行检查,这会带来许多问题:

  • 如果我一遍又一遍地运行相同的代码并检查额外的条件,可能会效率低下(尽管如果它是 const,也许编译器或 .NET 运行时足够聪明,可以避免这种情况?)
  • 您不能做会引发编译器错误的事情。在我上面的例子中,我用两种不同的类型定义了两次param。此外,UseOtherMethodSignature 可能不作为函数存在,如果两个块仅由 if/else 分隔,则无法编译。

那么,对于这类问题,公认的解决方案是什么?我的场景是我有多个版本的 Web 服务 API(根据您使用它的方式而有不同程度的差异),并且我希望能够在不注释/取消注释一堆代码或其他代码的情况下进行编译同样愚蠢的手动过程。

编辑

对于它的价值,我更喜欢编译时解决方案——在我的场景中,我知道当我编译要使用哪个版本时,我不需要弄清楚库的哪个版本在运行时在系统上可用。是的,这行得通,但似乎有点矫枉过正。

【问题讨论】:

  • 如果您使用 API 的 v1 构建,但在执行时只存在 v2,您会发生什么?请记住,虽然#define 本身仅适用于源文件级别,但您可以指定与项目属性相同的标记。
  • @JonSkeet 好问题,我想它会崩溃。我认为这正是发生的情况,例如,您从 C 代码构建二进制文件并动态链接它,但在没有该库/版本的系统上运行它。但在我的场景中,这是一个我有源代码的库,我将它编译成一个可部署的 ASP.NET 应用程序,所以这不是一个问题。在这种情况下,它更像是静态链接。

标签: c# compiler-construction


【解决方案1】:

您必须在项目级别定义编译符号。您可以在项目属性中执行此操作。这些符号可以通过#if 指令引用。

您还可以创建包含一个或另一个编译符号的项目构建配置,并检查项目文件中的配置以包含基于符号的一个或另一个 .dll 引用,以便您可以正确构建和调试两者只需从工具栏中的下拉列表中选择版本即可。

【讨论】:

    【解决方案2】:

    我建议使用 DI 框架来加载适当的类/dll。如果您可以重构代码以使用接口,那么您可以创建跨不同版本的抽象层。有关可用的不同框架,请参阅 this 链接。

    也许与您问题的编译时间性质保持一致的另一种解决方案是将生成的代码与T4 一起使用

    【讨论】:

    • 嗯……好吧,我以前见过 DI,但从未真正理解它的用途——也许就是这样。我想到了构建一个抽象的父类/接口,但这是一个使用wsdl.exe 生成的完整 API,并且有 250,000 行代码。我认为进行必要的重构对我来说是不切实际的:-)
    【解决方案3】:

    我的目标是将其抽象到不同的包装库中。它们将是 Visual Studio 中的独立项目,并引用不同版本的框架。

    // Shazaam contract.
    public interface IShazaamInvoker {
        Boolean Shazaam();
    }
    
    // ShazaamWrapper.v1.dll implementation
    public class ShazaamInvoker : IShazaamInvoker {
        public void Shazaam() {
            Int32 param = 42;
            return UseThisMethodSignature(param);
        }
    }
    
    // ShazaamWrapper.v2.dll implementation
    public class ShazaamInvoker : IShazaamInvoker {
        public void Shazaam() {
            Single param = 42f;
            return UseOtherMethodSignature(param);
        }
    }
    
    // Determine, at runtime, which wrapper to use.
    var invoker = (IShazaamInvoker)(/*HereBeMagicResolving*/)
    invoker.Shazaam();
    

    【讨论】:

    • 有趣,这可能是一个很好的解决方案,但这会导致两个版本都被编译,我必须决定在运行时使用哪个版本,对吗?没有办法在编译时完成这一切吗?
    • 没错,这意味着您的用户可以更改框架版本,而无需重新编译您的应用程序版本。您只需加载另一个版本的包装器,一切都会再次运行。这也将使单元测试变得更加容易,您可以模拟 IShazaamInvoker 并避免在测试中调用您的框架。
    猜你喜欢
    • 1970-01-01
    • 2012-06-07
    • 1970-01-01
    • 1970-01-01
    • 2018-05-27
    • 2010-09-29
    • 1970-01-01
    • 1970-01-01
    • 2018-10-01
    相关资源
    最近更新 更多