C# – How to implement an opaque handle in C#

cpolymorphism

I'm writing a library to be used by several applications.

Some interfaces which my library declares and uses, and which are implemented by the application, look something like this:

interface IOpaqueHandle : IDisposable {}

interface IPaintFactory
{
    IOpaqueHandle Create(string configData);
}

interface IPainter
{
    void Paint(IOpaqueHandle handle);
}

The Create method with configData parameter tells the application to allocate some resources and return a handle to those resources:

  • My library doesn't know what types of resource are allocated by the application, nor exactly what methods those resources support.

  • The resources probably come from various 3rd-party application-specific library, known to the application but not known by my library.

  • Create will be called multiple times with different parameter values, to allocate different resources each with their own IOpaqueHandle instance

The Paint method is an example of telling the application to do something with a specified resource.


My question is, what's the best way to implement the handle-to-resource mapping in the application?

For example, assuming that the application's resources are defined by a class named Resources, I can think of two ways to implement it.

  1. Using a subclass with an upcast:

    class Resource : IOpaqueHandle
    {
        internal static Resource getResource(IOpaqueHandle handle)
        {
            return (Resource)handle;
        }
    }
    
  2. Using a dictionary to map from the handle to the object:

    class Resource : IDisposable
    {
        internal static Resource getResource(IOpaqueHandle handle)
        {
            return s_dictionary[handle];
        }
    
        class Handle : IOpaqueHandle
        {
            public void Dispose()
            {
                Resource resource = getResource(this);
                resource.Dispose();
                s_dictionary.Remove(this);
            }
        }
    
        static Dictionary<IOpaqueHandle, Resource> s_dictionary =
            new Dictionary<IOpaqueHandle, Resource>();
    
        internal static IOpaqueHandle Create(string configData);
        {
            Handle handle = new Handle();
            Resource resource = new Resource(configData);
            s_dictionary.Add(handle, resource);
            return handle;
        }
    
        Resource(string configData) { ... }
    }
    

Is the subclass with an upcast simpler (to implement) and faster (at run-time) and therefore preferable?

Does the second solution have any advantages? It avoids an upcast, for what's that worth — is that good?


(First edit)

You might suggest declaring Paint as a method of the handle interface —
BUT that's not possible because of different lifetimes:

  • An IOpaqueHandle is a long-lived object, e.g. created once at object startup
  • An IPainter is a short-lived object, e.g. a new one is created each time the O/S window needs repainting.

A short-lived IPainter instance is created by the application and passed-in to my library, which calls its Paint method. The implementation of IPainter contains some short lived resources. The Paint method must combine the short-lived resources (contained in the IPainter) with the long-lived resources (contained in the handle).

Theoretically I could declare a method like this, instead of putting the Paint method in the IPainter interface:

interface IOpaqueHandle
{
    // paint using short-lived resources contained in IPainter
    void Paint(IPainter painter);
}

… but that amounts to the same problem, i.e. how to extract implementation-specific resources from the opaque IPainter interface.


(Second edit)

At the risk of making this question too long, here's some sample code.

The following code is in the library, in addition to the interfaces declared at the top.

class Node
{
    internal readonly IOpaqueHandle handle;

    // plus a lot of other data members
    // e.g. to determine whether this node is visible and should be painted

    internal Node(string configData, IPaintFactory factory)
    {
        // get the handle which we'll pass back to the application if we paint this node
        handle = factory.Create(configData);
    }

    internal bool IsVisible { get { ... } }
}

// facade
public class MyLibrary
{
    List<Node> nodes = new List<Node>();

    // initialize data using config data,
    // and using app-specific paint factory passed-in by application
    public void Initialize(List<string> configStrings, IPaintFactory factory)
    {
        configStrings.ForEach(configData => nodes.Add(new Node(configData, factory)));
    }

    // plus a lot of other methods to manipulate the Nodes

    // called from application when its window wants repainting
    public void OnPaint(IPainter painter)
    {
        nodes.ForEach(node => { if (node.IsVisible) painter.Paint(node.handle); });
    }
}

The above is a simplification. Because Node and MyLibrary are actually hundreds of classes, it wouldn't be convenient to wrap them all in a single generic class so that they share a common template parameter of type T.

The following code is in the application.
All the types in the Os namespace belong to some library
which the application is using and which my library doesn't know about.

class Resource : IOpaqueHandle
{
    readonly Os.Font font;
    readonly string text;

    internal Resource(string configData) { ... }

    // app-specific method using app-specific types
    // which my library doesn't know about and which
    // therefore isn't declared in the library's IOpaqueHandle interface
    internal void GraphicalPaint(Os.Graphics graphics)
    {
        graphics.DrawText(this.font, this.text);
    }
}

class PaintFactory : IPaintFactory
{
    public IOpaqueHandle Create(string configData)
    {
        return new Resource(configData);
    }
}

class Painter : IPainter, IDisposable
{
    internal readonly Os.Graphics graphics;

    internal Painter(Os.Graphics graphics)
    {
        this.graphics = graphics;
    }

    public void Dispose() { graphics.Dispose(); }

    public void Paint(IOpaqueHandle handle)
    {
        // use magic in question to retrieve app-specific resource from opaque handle
        Resource resource = (Resource)handle;

        // invoke app-specific method to paint this resource
        resource.GraphicalPaint(this.graphics);
    }
}

class Main : Os.Window
{
    MyLibrary myLibrary = new MyLibrary();

    void initialize(List<string> configStrings)
    {
        PaintFactory paintFactory = new PaintFactory();
        myLibrary.Initialize(configStrings, paintFactory);
    }

    // plus other methods to poke the data in MyLibrary

    // event handler called from Os when Window needs repainting
    void onPaint(Os.Graphics graphics)
    {
        using (Painter painter = new Painter(graphics))
        {
            myLibrary.OnPaint(painter);
        }
    }
}

Best Answer

The Plain answer is that any kind of mapping or up-casting is bad as it risks a run time error when it could otherwise be prevented by strongly typed classes.

Edit: OP included app code.

The solution is Generics, which allow you to specify a Type that your library has no knowledge of.

IPaintFactory<T>
{
    IOpaqueHandle<T> Create(config);
}

IOpaqueHandle<T>
{
    T Resource {get;}
}

This allows the app to specific the type of resource underneath the handler. painter can then be:

App.Painter : IPainter<App.MyResource>
{
    void Paint(IOpaqueHandle<App.MyResource> handle)
    {
        handle.Resource.MySecretPaintMethod(); 
    }
}

In Your case the type you need to use but the library doesn't know about is OS.Graphics.

Because the library class itself has the method which needs this type myLibrary.OnPaint The library will need to be a generic type. The Generic type parameter propagates up to where this function is specified.

Possibly you could refactor so that some sub class of library has the OnPaint method, then you could limit the generics to that sub type.

Related Topic