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?
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.
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>
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With