“Implementing” a non-public interface in .NET Core with DispatchProxy

· 1149 words · 6 minutes to read

Reflection is a tremendously powerful concept in .NET, which every C# developer, sooner or later, ends up working with. It is very useful for a lot of perfectly honest scenarios such as for example assembly scanning, type discovery or all kinds of application composability features.

However, it can also be used to circumvent the public API surface of the dependencies you consume - to modify them or gain access to things that the author of your dependency didn’t exactly envision. That said, this process of “hacking around” is very typical for C# development world and, albeit somewhat risky, it sometimes might be the only way out of a coding jam you are in.

Things start to get interesting if you are forced to implement a non-public (for example internal) interface. At that point, the “basic” reflection can’t help anymore, so let’s have a look at how we can do it.

Sample problem 🔗

Let’s imagine that you are consuming a 3rd party library that contains the following internal Greeter type:

internal class Greeter
{
    public static void Greet(IGreeting greeting)
    {
        Console.WriteLine(greeting.Message);
    }
}

Now, let’s say you’d want to use that type with reflection - invoke the Greet method. To do so, you need an instance of a type implementing the IGreeting interface, as it must be passed into the method. The interface is shown next:

internal interface IGreeting
{
    string Message { get; }
}

The caveat is - there is no implementation of it for you to pick up. Instead, to be able to use the Greeter, you must provide your own implementation of IGreeting.

Of course implementing an interface in C# is simple - but implementing an interface that you picked up with reflection since it’s non public? Well, that’s a bit of a problem, isn’t it? This is also illustrated in the code below, with the sample code running in a different assembly than our Greeter and IGreeting types.

class Program
{
    static void Main(string[] args)
    {
        // find the non-public Greeter type                
        var greeterType = Assembly.Load("Library").GetType("Library.Greeter");
        
        // pick up the Greet method
        var greetMethod = internalType.GetMethod("Greet", BindingFlags.Public | BindingFlags.Static);

        // invoke the method. however...
        // ...we need to have an instance of IGreeting, somehow
        var greeting = greetMethod.Invoke(null, new[] { ??? });
        Console.WriteLine();
    }
}

DispatchProxy 🔗

Enter DispatchProxy. It is a type that exists in .NET Core since the beginning of the platform and provides a mechanism for instantiating proxy objects and handling their method dispatch. The typical usage example of the DispatchProxy is as follows:

var proxy = DispatchProxy.Create<IFoo, FooProxy>();

In our example IFoo is the interface we want to implement. The great strength of the DispatchProxy is the following - it allows us to create a FooProxy which is a type that can be potnetially used as if it was an IFoo, without actually “implementing it” (or it could forward to another type that actually pretends to be IFoo too).

However, when using the above API, the Type of the interface the proxy implements needs to be known at compile time, which is not ideal at our case - because in case of non-public interfaces, we only get a hold of it at runtime. No worries - we will solve this with reflection (again). This is illustrated below (imagine IFoo is non-public):

var internalType = Assembly.Load("Library").GetType("IFoo");
var proxy = typeof(DispatchProxy).GetMethod(nameof(DispatchProxy.Create)).MakeGenericMethod(internalType, typeof(FooProxy)).Invoke(null, null);

In the end it’s a simple trick - we end up using the same API as before, but we can dynamically provide the necessary type parameters, instead of having to know them at compile time in order to use them in the generics.

In our specific Greeting example, the code to create the proxy would look like this (for cleaner separation, wrapped into a factory class):

public class GreetingFactory
{
    public static object Create()
    {
        var internalType = Assembly.Load("Library").GetType("Library.IGreeting");
        return typeof(DispatchProxy).GetMethod(nameof(DispatchProxy.Create)).MakeGenericMethod(internalType, typeof(GreetingProxy)).Invoke(null, null);
    }
}

Now the last piece of the puzzle is to actually implement the GreetingProxy itself. It is shown next, and it is a subclass of DispatchProxy:

public class GreetingProxy : DispatchProxy
{
    private GreetingImpl _impl;

    public GreetingProxy()
    {
        _impl = new GreetingImpl();
    }

    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        return _impl.GetType().GetMethod(targetMethod.Name).Invoke(_impl, args);
    }

    private class GreetingImpl // : IGreeting - doesn't implement IGreeting but mimics it
    {
        public string Message => "hello world";
    }
}

As you see, the class acts as the gateway between the potential callers and the actual implementation of our IGreeting - after all, that is the point of the proxy. The “implementation” (I use quotation marks, since we can’t really implement a non-public interface), or rather, a type that mimics the interface structure takes the form of a private class GreetingImpl, and it contains the necessary public Message property. It doesn’t have to be done this way, it’s just my approach here, and I kinda like it.

Whenever the proxy is invoked, we get MethodInfo information of which member of the interface is being requested - so we simply redirect it to the corresponding member of the hidden GreetingImpl which is structurally the same.

Overall our code to consume this whole shebang looks like this now:

class Program
{
    static void Main(string[] args)
    {
        var internalType = Assembly.Load("Library").GetType("Library.Greeter");
        var greetMethod = internalType.GetMethod("Greet", BindingFlags.Public | BindingFlags.Static);

        var proxy = GreetingFactory.Create();
        Console.WriteLine(greetMethod.Invoke(null, new[] { proxy }));
    }
}

And what does it do? It prints hello world like we defined in the GreetingImpl served up via our proxy object. Of course the net result here is that we managed to “implement” and use a non-public interface against another non-public API.

Is this useful in real world? 🔗

Like with everything, I guess the answer is - it depends - after all it’s a highly specialized API. This technique (proxy objects) is used in products like ORMs or mocking frameworks. Also, if you are consuming complex 3rd part frameworks or libraries and need to do lots of reflection against that, chances are sooner or later you will need to use DispatchProxy.

In fact, if you are interested in a real world usage examples, you can have a look at our OmniSharp project. OmniSharp uses the Roslyn compiler to provide C# intellisense and other language services to a number of editors, including VS Code. However, unfortunately, Roslyn is not particularly generous with the APIs it offers to the public, so we have to do a lot of stuff with reflection. And as a matter of fact, we also have to use DispatchProxy in quite a few places to be able to offer the users feature like being able to extract interface from a type. This is - on one hand - not great because things can break very easily, but the value to the customers is unquestionable so we chose to do it anyway.

If you are interested in the demo code, it’s available on Github. Hopefully you will find this useful at some point.

About


Hi! I'm Filip W., a cloud architect from Zürich 🇨🇭. I like Toronto Maple Leafs 🇨🇦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁢󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP