FANDOM


This page discusses the technique for decoupling .NET assemblies.

Problem statementEdit

In .NET, you can use an Interface to decouple a caller from the implementation, but the caller still has to instaitate a concrete class. For instance, if there's a .NET assembly called DX.DLL with a public class called DataManager (implementing class IDataManager), then a typical client would create and obtain a reference to an instance to the class like so:

IDataManager am = new DataManager();

In order for this code to compile, the client module has to add a Reference to the .NET assemblies that contain DataManager and IDataManager.

It's easy enough to move IDataManager to a separate .NET assembly (call it DXInterfaces.dll) which rarely changes, but the client still has to point to DX.DLL because it contains the DataManager class. Because of this, each time DX.DLL is built, so does the client, creating an unnecessary build dependency. Furthermore, it makes it difficult to swap in an alternative implementation of IDataManager that the client can use without modifying the client.

SolutionEdit

The technique described below uses the Activator.CreateInstance method to create an instance of a class there by eliminating the tight binding from the client to the implementation of the class it's calling.

A special "factory" class is created like so:

    public class DataManagerFactory
    {
        public static IDataManager CreateInstance()
        {
            return (IDataManager)Activator.CreateInstance("DX, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "RuntimeLib.DataManager").Unwrap();
        }
    }

The instantiation code than changes to:

IDataManager am = DataManagerFactory.CreateInstance();

Since the DataManager client is not referenced directly (i.e. the class name is passed in quotes), the compling the client doesn't require a reference to the class that contains it (i.e. DX.DLL).

Equally, it must be understood that this can also be a disadvantage: since the client will compile without the presence of the server component, it is not aware of it, and won't automatically deploy it to the debug/release folder during development, and it cannot check whether the interface (IDataManager in this case) is actually implemented by the class being instantiated. If is not, a runtime error rather than a build error will occur. This means the onus is on the developer to ensure the correct assemblies are deployed on the target system. However, this is no different than bindings in the COM world, or with explictly loaded DLLs in pure-Win32, and the advantages of decoupling clients in general far outweigh these issues.

Creating a factory class for your own classes

  • Copy / paste the above code
  • Replace 'RuntimeLib.DataManager' with the name of your class. Note that the class has to be fully qualified, including the namespace.
  • Replace 'DX' with the name of your DLL, and fix the version number.
  • Replace IDataManager with the name of your interface.

How to externalize the class / assembly configurationEdit

One disadvantage of the above technique is that the name of the assembly that contains the class being instantiated is hard-coded inside of the client code. One can easily use the app.config facility of .NET assemblies to externalize the class and assembly information so that they can be changed outside the assmebly. This way, the client can be redirected to use a different implementation of the interface.

The following code expands on the code above. If the class is specified in the app.config file, it will used the settings from the config file, otherwise it will use the default class and assembly. That way, the app.config file would not be required, but may be used to override.

    public class DataManagerFactory
    {
        // Suffix in the app.config
        const string suffix = "Override:";

        // Name of the class to create, include the namespace.
        const string defaultClass = "RuntimeLib.DataManager";
        const string defaultAssembly = "DX, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";

        public static IDataManager CreateInstance()
        {
            string entry = System.Configuration.ConfigurationManager.AppSettings[suffix + defaultClass];
            if (entry != null)
            {
                string[] substrings = Regex.Split(entry, ":");
                return (IDataManager)Activator.CreateInstance(substrings[1], substrings[0]).Unwrap();
            }
            else
            {
                return (IDataManager)Activator.CreateInstance(defaultAssembly, defaultClass).Unwrap();
            }
        }
    }

In the above example, calling DataManagerFactory.CreateInstance by default instantiates class RuntimeLib.DataManager. But you can override that by adding a key to your app.config file:

<add key="Override:RuntimeLib.DataManager" value="MyTestClasses.DataManager:MyTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"></add>

The 'value' represents the code that you want to use instead of the default. The 'value' contains the class name and assembly name separate by a colon.

When clients requests the factory to instantiate RuntimeLib.DataManager, it creates MyTestClasses.DataManager in MyTests.dll instead.

If your project doesn't have an app.config file, do "Add new item" on the Visual Studio project and select "Application Configuration". Here's an example app.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <appSettings>
        <add key="Override:RuntimeLib.DataManager" value="MyTestClasses.DataManager:MyTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"></add>
    </appSettings>
</configuration>

You will need to add "System.Configuration" to the project's references.

Adding an entry to the app.config file:

  • Copy / paste the 'add' node into your app.config file.
  • Replace 'RuntimeLib.DataManager' with the name of your class. Note that the class has to be fully qualified, including the namespace.
  • Replace 'MyTests' with the name of your DLL, and fix the version number.

ReferencesEdit

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.