This is the sixth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#
Let’s start with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public interface IHaveASandwich { void Foo(); } public class ASandwichProvider : IHaveASandwich { public void Foo() { Console.WriteLine("ASandwichProvider — Foo"); } public void Bar() { Console.WriteLine("ASandwichProvider — Bar"); } } public class Service { public void DoSomethingWithSandwich(IHaveASandwich a) { a.Foo(); var provider = (ASandwichProvider) a; provider.Bar(); } } class Program { static void Main(string[] args) { var a = new ASandwichProvider(); new Service().DoSomethingWithSandwich(a); } } |
When we run this we get:
1 2 |
ASandwichProvider - Foo ASandwichProvider - Bar |
We have interface and one implementer. Also, we have a service which knows internal details of class implementing the interface. This might look suspicious but imagine the following situation: IHaveASandwich
interface is in some kind of library which uses only stuff defined in the interface. However, Service
is a concrete client of the library which needs to store some additional details required for implementation (e.g., serialization stuff). Client works with IHaveASandwich
most of the time, but sometimes it needs to access implementation details so it casts the object to the specific type.
Now, let’s try to write a proxy for that interface, e.g., to add operator overloading:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public class IHaveASandwichWrapper : IHaveASandwich { private IHaveASandwich target; public IHaveASandwichWrapper(IHaveASandwich target) { this.target = target; } public void Foo() { Console.WriteLine("IHaveASandwichWrapper — Foo"); target.Foo(); } public static IHaveASandwichWrapper operator +(IHaveASandwichWrapper a, IHaveASandwichWrapper b) { Console.WriteLine("Adding two sandwiches"); return a; } } public static class IHaveASandiwchExtensions { public static IHaveASandwichWrapper Wrap(this IHaveASandwich target) { return new IHaveASandwichWrapper(target); } } class Program { static void Main(string[] args) { var a = new ASandwichProvider(); var wrappedA = a.Wrap(); var sum = wrappedA + wrappedA; new Service().DoSomethingWithSandwich(wrappedA); } } |
And the output is:
1 2 3 4 5 6 7 |
Adding two sandwiches IHaveASandwichWrapper - Foo ASandwichProvider - Foo Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'ConsoleApplication.IHaveASandwichWrapper' to type 'ConsoleApplication.ASandwichProvider'. at ConsoleApplication.Service.DoSomethingWithSandwich(IHaveASandwich a) in C:\Users\user\Documents\Visual Studio 2015\Projects\ConsoleApplication\ConsoleApplication\Program.cs:line 28 at ConsoleApplication.Program.Main(String[] args) in C:\Users\user\Documents\Visual Studio 2015\Projects\ConsoleApplication\ConsoleApplication\Program.cs:line 70 |
So, wrapping works, adding works (via operator +), but now it fails when casting IHaveASandwichWrappper
to ASandwichProvider
. Can we fix this?
Well, we can. But it is not that easy. If you try to google this stuff you will find out that it is impossible to overwrite casting operator from and to interface (see Eric Lippert’s answers on SO: here and especially here). So what can we do?
Implementation
First, we need to introduce yet another interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public interface IAmASandwichProvider { void Bar(); } public class ASandwichProvider : IHaveASandwich, IAmASandwichProvider { public void Foo() { Console.WriteLine("ASandwichProvider — Foo"); } public void Bar() { Console.WriteLine("ASandwichProvider — Bar"); } } public class Service { public void DoSomethingWithSandwich(IHaveASandwich a) { a.Foo(); var provider = (IAmASandwichProvider) a; provider.Bar(); } } |
Now we cast to interface instead of casting to concrete implementation.
Next, our wrapper needs to be proxy with implemented interface, for which we can use Castle.DynamicProxy (I use package Castle.Core 4.1.1):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public static class IHaveASandiwchExtensions { public static IHaveASandwichWrapper Wrap(this IHaveASandwich target) { ProxyGenerator generator = new ProxyGenerator(); var interfacesToProxy = target .GetType() .GetInterfaces() .Where(t => t != typeof(IHaveASandwich)) .ToArray(); if (interfacesToProxy.Length == 0) { return new IHaveASandwichWrapper(target); } var options = new ProxyGenerationOptions(); options.AddMixinInstance(target); return (IHaveASandwichWrapper)generator.CreateClassProxy(typeof(IHaveASandwichWrapper), interfacesToProxy, options, new object[] { target }); } } |
We create proxy class implementing all additional interfaces (beside IHaveASandwich
) but still treat it as a wrapper. Now our code works:
1 2 3 4 |
Adding two sandwiches IHaveASandwichWrapper - Foo ASandwichProvider - Foo ASandwichProvider - Bar |
So now library can give us operators (or any other extension which need to be implemented directly on class, not on interface) and we still can work with internal implementation of our class despite the fact that we get the wrapper instead of our class (which are in different inheritance hierarchy).