I was actually doing something like this. Assemblies were probed if they exist/will work.
You can do this in many ways, of course, but that's what I ended with:
- Extracted all the functionality of this class into interface (let's call it: IExcel) and added IsAvailable property to it
- Implemented fake class implementing IExcel, and returning 'false' from IsAvailable (and, of course, throwing NotSupportedException from other methods).
- Create new assembly (important: NEW ASSEMBLY) with real implementation of IExcel
- Implement Factory class with 'Create' which will decide which one should be returned and catch exception on resolve (or test)
Assembly: MyFacade
// the interface
public interface IExcel
{
bool IsAvailable { get; }
// your stuff
}
// the fake implementation
public class FakeExcel: IExcel
{
public IsAvailable { get { return false; } }
// your stuff should probalby throw NotSupportedException
}
Assembly: MyImplementation
// real implementation
public class RealExcel: IExcel
{
private bool? _isAvailable;
public bool IsAvailable
{
// return value if it is already known, or perform quick test
get { return (_isAvailable = _isAvailable ?? PerformQuickTest()); }
}
private bool PerformQuickTest()
{
try
{
// ... do someting what requires Excel
// it will just crash when it cannot be found/doesn't work
}
catch // (Exception e)
{
return false;
}
return true;
}
}
Assembly: MyFacadeFactory
public class ExcelFactory
{
public static IExcel Create()
{
// delay resolving assembly by hiding creation in another method
return Try(NewRealExcel) ?? new FakeExcel();
}
private static IExcel Try(Func<IExcel> generator)
{
try
{
var result = generator();
if (result.IsAvailable)
return result;
}
catch // (Exception e)
{
// not interested
}
return null; // didn't work exception or IsAvailable returned 'false'
}
// this could be implemented as delegate but it's
// safer when we put NoInlining on it
[MethodImpl(MethodImplOptions.NoInlining)]
private static IExcel NewRealExcel()
{
return new RealExcel();
}
}
What will happen?
- If you have Excel and MyImplementation assembly can be found, it will be loaded, RealExcel class will be created and then used
- If you don't have Excel but you do have MyImplementation assembly, it will be loaded, RealExcel class will be created, but will fail on 'PerformQuickTest' so FakeExcel will be used
- If MyImplementation assembly cannot be found (you did not include it) it will fail when RealExcel is created in MyFacade, so FakeExcel will be used
You can of course do all those things with dynamic loading and reflection (less lines of code) but a little bit clunky to use. I've found this method most reflection-less.