As pointless as this construct might be, for some reason it intrigued me and I quickly mocked up a Castle DynamicProxy implementation for creating objects that bundles together multiple interfaces.
The mixin factory provides two methods:
object CreateMixin(params object[] objects)
Returns an object that implements any number of interfaces. In order to get to the implemented interface you must cast the returned object to that interface.
TMixin CreateMixin<TMixin, T1, T2>(T1 obj1, T2 obj2)
Returns an interface that implements the other two interfaces to achieve strong typing. That combining interface must exist at compile time.
Here are the objects:
public interface ICat {
void Meow();
int Age { get; set; }
}
public interface IDog {
void Bark();
int Weight { get; set; }
}
public interface IMouse {
void Squeek();
}
public interface ICatDog : ICat, IDog {
}
public interface ICatDogMouse : ICat, IDog, IMouse {
}
public class Mouse : IMouse {
#region IMouse Members
public void Squeek() {
Console.WriteLine("Squeek squeek");
}
#endregion
}
public class Cat : ICat {
#region ICat Members
public void Meow() {
Console.WriteLine("Meow");
}
public int Age {
get;
set;
}
#endregion
}
public class Dog : IDog {
#region IDog Members
public void Bark() {
Console.WriteLine("Woof");
}
public int Weight {
get;
set;
}
#endregion
}
Take note of ICatDog
interface.I thought that it would be pretty cool if the dynamic proxy returns something that is strongly typed and can be used where either interface is accepted. This interface will need to be defined at compile time if strong typing is indeed desireable. Now for the factory:
using Castle.DynamicProxy;
public class MixinFactory {
/// <summary>
/// Creates a mixin by comining all the interfaces implemented by objects array.
/// </summary>
/// <param name="objects">The objects to combine into one instance.</param>
/// <returns></returns>
public static object CreateMixin(params object[] objects) {
ProxyGenerator generator = new ProxyGenerator();
ProxyGenerationOptions options = new ProxyGenerationOptions();
objects.ToList().ForEach(obj => options.AddMixinInstance(obj));
return generator.CreateClassProxy<object>(options);
}
/// <summary>
/// Creates a dynamic proxy of type TMixin. Members that called through this interface will be delegated to the first matched instance from the objects array
/// It is up to the caller to make sure that objects parameter contains instances of all interfaces that TMixin implements
/// </summary>
/// <typeparam name="TMixin">The type of the mixin to return.</typeparam>
/// <param name="objects">The objects that will be mixed in.</param>
/// <returns>An instance of TMixin.</returns>
public static TMixin CreateMixin<TMixin>(params object[] objects)
where TMixin : class {
if(objects.Any(o => o == null))
throw new ArgumentNullException("All mixins should be non-null");
ProxyGenerator generator = new ProxyGenerator();
ProxyGenerationOptions options = new ProxyGenerationOptions();
options.Selector = new MixinSelector();
return generator.CreateInterfaceProxyWithoutTarget<TMixin>(options, objects.Select(o => new MixinInterceptor(o)).ToArray());
}
}
public class MixinInterceptor : IInterceptor {
private object m_Instance;
public MixinInterceptor(object obj1) {
this.m_Instance = obj1;
}
public Type ObjectType {
get {
return m_Instance.GetType();
}
}
#region IInterceptor Members
public void Intercept(IInvocation invocation) {
invocation.ReturnValue = invocation.Method.Invoke(m_Instance, invocation.Arguments);
}
#endregion
}
public class MixinSelector : IInterceptorSelector{
#region IInterceptorSelector Members
public IInterceptor[] SelectInterceptors(Type type, System.Reflection.MethodInfo method, IInterceptor[] interceptors) {
var matched = interceptors
.OfType<MixinInterceptor>()
.Where(mi => method.DeclaringType.IsAssignableFrom(mi.ObjectType))
.ToArray();
if(matched.Length == 0)
throw new InvalidOperationException("Cannot match method " + method.Name + "on type " + method.DeclaringType.FullName + ". No interceptor for this type is defined");
return matched;
}
#endregion
}
Usage is best explained in these unit tests. As you can see the second method returns a type safe interface that seemlessly bundles any number of interfaces.
[TestMethod]
public void CreatedMixinShouldntThrow() {
ICat obj1 = new Cat();
IDog obj2 = new Dog();
var actual = MixinFactory.CreateMixin(obj1, obj2);
((IDog)actual).Bark();
var weight = ((IDog)actual).Weight;
((ICat)actual).Meow();
var age = ((ICat)actual).Age;
}
[TestMethod]
public void CreatedGeneric3MixinShouldntThrow() {
ICat obj1 = new Cat();
IDog obj2 = new Dog();
IMouse obj3 = new Mouse();
var actual = MixinFactory.CreateMixin<ICatDogMouse>(obj1, obj2, obj3);
actual.Bark();
var weight = actual.Weight;
actual.Meow();
var age = actual.Age;
actual.Squeek();
}
I have blogged about this in more detail and provided source and tests. You can find it here.