Работа с необязательными зависимостями (С#)

У нас есть приложение, которое необязательно интегрируется с TFS, однако, поскольку интеграция необязательна, я, очевидно, не хочу, чтобы все машины нуждались в сборках TFS как требование.

Что мне делать?

  • Хорошо ли мне ссылаться на библиотеки TFS в моих основных сборках и просто следить за тем, чтобы я ссылался только на связанные с TFS объекты, когда я использую интеграцию TFS.
  • В качестве альтернативы более безопасным вариантом будет ссылка на библиотеки TFS в отдельной сборке "TFSWrapper":

    а. Это нормально для меня напрямую ссылаться на эту сборку (опять же, пока я осторожен в том, что я называю)

    б. Должен ли я вместо этого выставлять набор интерфейсов для моей сборки TFSWrapper для реализации, а затем создавать эти объекты, используя при необходимости отражение.

1 кажется мне рискованным, на оборотной стороне 2b кажется чрезмерным - я, по сути, создаю подключаемую систему.

Конечно, должен быть более простой способ.

Ответ 1

Самый безопасный способ (т.е. самый простой способ не сделать ошибку в вашем приложении) может быть следующим.

Сделайте интерфейс, который абстрагирует ваше использование 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); остальная часть вашего кода может вызывать методы ITfs вашего TfsFactory.instance, не зная, установлена ​​ли 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() + "!");
        }
    }
}

Это результаты тестов (с использованием MSVС# 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>

Ответ 3

Концепция "плагина" может быть способом, и она также может позволить вам (позже) расширить ваше приложение для работы с другими продуктами, кроме TFS, если это необходимо. Вариант 2a будет таким же "рискованным" (если отсутствует связанный файл) в качестве опции 1.

Вы можете создать сборку с необходимыми интерфейсами для своей конкретной цели и ссылаться на эту сборку как из вашего приложения, так и из "подключаемого модуля TFS". Последний затем обеспечивает реализацию ваших интерфейсов и использует TFS для выполнения операций. Приложение может динамически загружать сборку и создавать экземпляры требуемых типов подключаемых модулей (через Activator и т.д.) И направить эти экземпляры на ваши интерфейсы.

Фактически, если вы наследуете эти типы от MarshalByRef, вы можете даже загрузить их в другой AppDomain и тем самым сделать чистое разделение ваших плагинов, а также сделать их неактивными.