处理可选依赖项(C#)

14
我们有一个应用程序,可以选择集成TFS,但由于集成是可选的,我显然不想要所有需求TFS组件的机器
我该怎么办?
1. 我能在我的主程序集中引用TFS库,只要确保在使用TFS集成时只引用与TFS相关的对象吗? 2. 或者更安全的选择是在某个单独的“TFSWrapper”程序集中引用TFS库:
a. 然后我可以直接引用该程序集(只要我注意调用什么)
b. 我应该暴露一组接口供我的TFSWrapper程序集实现,然后在需要时使用反射实例化这些对象。
对我来说,第1种方式似乎存在风险,而第2b种方法则过于复杂 - 本质上我将构建一个插件系统。
肯定有更简单的方法。

“TFS” 在这里是指 “Team Foundation System”,对吗? - Jeff Sternal
是的,但我认为这个问题与TFS关系不大,更多地涉及到引用程序集的最佳方式,因为这些程序集可能不存在于某些用户的机器上。 - Justin
糟糕,我没有仔细阅读第一句话,所以我没有意识到你所说的“可选”的意思是“可能不在用户的计算机上存在”。 - Jeff Sternal
3个回答

7

最安全的方式(即在应用程序中避免错误的最简单方法)可能如下所示。

创建一个抽象您使用TFS的接口,例如:

interface ITfs
{
  bool checkout(string filename);
}

使用TFS编写实现此接口的类:

class Tfs : ITfs
{
  public bool checkout(string filename)
  {
    ... code here which uses the TFS assembly ...
  }
}

编写另一个类来实现此接口,而不使用TFS:

class NoTfs : ITfs
{
  public bool checkout(string filename)
  {
    //TFS not installed so checking out is impossible
    return false;
  }
}

在某个地方使用单例模式:

static class TfsFactory
{
  public static ITfs instance;

  static TfsFactory()
  {
    ... code here to set the instance
    either to an instance of the Tfs class
    or to an instance of the NoTfs class ...
  }
}

现在只有一个地方需要小心(即TfsFactory构造函数);您的其余代码可以调用TfsFactory.instance的ITfs方法,而不知道是否安装了TFS。
回答以下最近的评论:
根据我的测试(我不知道这是否是“定义行为”),当您调用依赖于缺失程序集的方法时,会抛出异常。因此,重要的是将依赖于缺失程序集的代码封装在至少一个单独的方法(或单独的类)中。在您的程序集中。
例如,如果Talk程序集丢失,则以下内容将无法加载:
using System;
using OptionalLibrary;

namespace TestReferences
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            if (args.Length > 0 && args[0] == "1") {
                Talk talk = new Talk();
                Console.WriteLine(talk.sayHello() + " " + talk.sayWorld() + "!");
            } else {
                Console.WriteLine("2 Hello World!");
            }
        }
    }
}

以下内容将被加载:
using System;
using OptionalLibrary;

namespace TestReferences
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            if (args.Length > 0 && args[0] == "1") {
                foo();
            } else {
                Console.WriteLine("2 Hello World!");
            }
        }

        static void foo()
        {
            Talk talk = new Talk();
            Console.WriteLine(talk.sayHello() + " " + talk.sayWorld() + "!");
        }
    }
}

以下是使用MSVC# 2010和.NET在Windows上的测试结果:

C:\github\TestReferences\TestReferences\TestReferences\bin\Debug>TestReferences.exe
2 Hello World!

C:\github\TestReferences\TestReferences\TestReferences\bin\Debug>TestReferences.exe 1

Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'OptionalLibrary, Version=1.0.0.0,
 Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
   at TestReferences.MainClass.foo()
   at TestReferences.MainClass.Main(String[] args) in C:\github\TestReferences\TestReferences\TestReferences\Program.cs:
line 11

C:\github\TestReferences\TestReferences\TestReferences\bin\Debug>

这将使TfsFactory依赖包含Tfs类的程序集。由于应用程序中使用了TfsFactory,因此仍需要使用“可选”的Tfs dll来分发软件。 :-) - Sergiy Belozorov
@SergiyByelozyorov 我相信(如有错误请指出)您需要 DLL 以进行构建,但是您可以在没有 DLL 的情况下进行发布/部署...如果目标计算机上不存在该程序集,则该程序集仍将被加载,并且除 TfsFactory 方法外的所有内容都将运行正确。 - ChrisW
@ChrisW:不对。我测试过了。即使使用TfsFactory的代码没有执行,应用程序仍然因为缺少DLL而无法运行。附注:我是使用Mono测试的,.NET可能会有所不同。 - Sergiy Belozorov
@SergiyByelozyorov 我也试过了(使用的是.NET而不是Mono)。 应用程序启动,但在调用一个依赖于未定义类型实现的方法时会出现异常。 因此,如果您的 if 语句防止调用该方法,则它应该有效,如果您的 if 语句保护在包含依赖于该类型的代码的方法内部,则可能无法正常工作。 - ChrisW
1
@SergiyByelozyorov 将您的 Talk 依赖代码移动到一个单独的方法中,例如:`class MainClass { public static void Main(string[] args) { if (args.Length > 0 && args[0] == "1") { foo(); } else { Console.WriteLine("2 Hello World!"); } } static void foo() { Talk talk = new Talk(); Console.WriteLine(talk.sayHello() + " " + talk.sayWorld() + "!"); } }` - ChrisW
显示剩余5条评论

1

那看起来相当有趣,虽然对于我目前所做的事情可能有点过头了!不过我以后可能会再看一下。 - Justin

0

使用“插件”概念可能是一种解决方法,如果需要,它也可能允许您扩展应用程序以与除 TFS 之外的其他产品配合使用。选项 2a 和选项 1 一样“冒险”(如果链接文件丢失,则失败)。

您可以创建一个具有所需接口的程序集,从您的应用程序和“TFS 插件”中引用此程序集。然后,后者提供实现您的接口并使用 TFS 执行操作的功能。 应用程序可以动态加载程序集,并通过 Activator 等方式创建所需插件类型的实例,并将这些实例强制转换为您的接口。

事实上,如果让这些类型继承自 MarshalByRef,甚至可以将它们加载到另一个 AppDomain 中,从而对插件进行清晰区分,并使它们可卸载。


我正在尝试实现一个插件系统。然而,有时我想使用另一个程序集中引入的类型。使用此类型的代码将被保护在一个“if”语句中,以确保它仅在库可用且已加载时使用。在C#中有没有这样做的方法? - Sergiy Belozorov
@SergiyByelozyorov,是的,这可以通过后期绑定(例如反射或动态)来完成。如果该类型可以实现一些始终可用的接口,那么使用起来会更加方便。 - Lucero

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接