Home Blog Patterns and Practices Singleton per Instance with ArcObjects
Singleton per Instance with ArcObjects
Monday, 17 January 2011 21:37

I've been writing some code that calls for a single instance of a class per geodatabase workspace. This is important when trying to maintain things like an Identity Map – you don't want multiple instances of a class that access the same geodatabase floating around. So, I started with the GoF Singleton pattern:

    public class Singleton
{
private static Singleton _instance;

private Singleton()
{
}

public static Singleton Instance()
{
if (_instance == null)
{
_instance = new Singleton();
}

return _instance;
}
}

This pattern creates one and only one instance of the Singleton class within the application. However, I want one and only one instance for each Workspace that I open. When I query for an instance of the Singleton, I need to pass in the Workspace that corresponds to the Singleton instance:

    public static Singleton Instance(IWorkspace workspace)

Internally, I defined a Dictionary that maintains a mapping of instances of the Singleton objects and their corresponding Workspaces. This mapping is maintained for all instances of the Singleton class, and so is a static member:

    private static Dictionary<IWorkspace, Singleton> _instances = 
new Dictionary<IWorkspace, Singleton>();

Each Singleton instance maintains a reference to its own Workspace:

    private IWorkspace _workspace;

When a client calls the Instance method and passes a Workspace, the Instance method checks the Dictionary to see if an instance for the Workspace already exists; if it does, then that instance is returned; if not, then a new instance is created for that Workspace and returned.

Following is the complete code for the implementation of the Singleton per Workspace:

    public class Singleton
{
private static Dictionary<IWorkspace, Singleton> _instances =
new Dictionary<IWorkspace, Singleton>();
private static readonly object _lock = new object();
private IWorkspace _workspace;

private Singleton(IWorkspace workspace)
{
_workspace = workspace;
}

public static Singleton Instance(IWorkspace workspace)
{
lock (_lock)
{
if (_instances.ContainsKey(workspace))
{
return _instances[workspace];
}

Singleton instance = new Singleton(workspace);
_instances.Add(workspace, instance);

return instance;
}
}
}

It's important to note that we have the right test for identity. In this example, I know that I'm using the same Workspace object to create a Singleton instance, so I can use the identity operator. If I'm not sure of that, then I would need to devise some scheme to test for identity.

So, let's make this generic for any singleton ArcObjects type:

    public class Singleton<T>
{
private static Dictionary<T, Singleton<T>> _instances =
new Dictionary<T, Singleton<T>>();
private static readonly object _lock = new object();
private T _internalObject;

private Singleton(T internalObject)
{
_internalObject = internalObject;
}

public static Singleton<T> Instance(T internalObject)
{
lock (_lock)
{
if (_instances.ContainsKey(internalObject))
{
return _instances[internalObject];
}

Singleton<T> instance = new Singleton<T>(internalObject);
_instances.Add(internalObject, instance);

return instance;
}
}
}