Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dealing with optional dependencies (C#)

We have an app which optionally integrates with TFS, however as the integration is optional I obviously don't want to have all machine need the TFS assemblies as a requirement.

What should I do?

  1. Is it ok for me to reference the TFS libraries in my main assemblies and just make sure that I only reference TFS related objects when I'm using TFS integration.
  2. Alternatively the safer option would be to reference the TFS libraries in some separate "TFSWrapper" assembly:

    a. Is it then ok for me to reference that assembly directly (again as long as I'm careful about what I call)

    b. Should I instead be exposing a set of interfaces for my TFSWrapper assembly to implement, and then instantiate those objects using reflection when required.

1 seems risky to me, on the flip side 2b seems over-the-top - I would essentially be building a plug-in system.

Surely there must be a simpler way.

like image 740
Justin Avatar asked Sep 14 '09 14:09

Justin


1 Answers

The safest way (i.e. the easiest way to not make a mistake in your application) might be as follows.

Make an interface which abstracts your use of TFS, for example:

interface ITfs
{
  bool checkout(string filename);
}

Write a class which implements this interface using TFS:

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

Write another class which implements this interface without using TFS:

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

Have a singleton somewhere:

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 ...
  }
}

Now there's only one place which needs to be careful (i.e. the TfsFactory constructor); the rest of your code can invoke the ITfs methods of your TfsFactory.instance without knowing whether TFS is installed.


To answer recent comments below:

According to my tests (I don't know whether this is 'defined behaviour') an exception is thrown when (as soon as) you call a method which depends on the missing assembly. Therefore it's important to encapsulate your code-which-depends-on-the-missing-assembly in at least a separate method (or a separate class) in your assembly.

For example, the following won't load if the Talk assembly is missing:

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!");
            }
        }
    }
}

The following will load:

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() + "!");
        }
    }
}

These are the test results (using MSVC# 2010 and .NET on 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>
like image 192
ChrisW Avatar answered Oct 06 '22 01:10

ChrisW