Se puede crear un DynamicObject
que reenvía las llamadas que recibe a una lista de objetivos, en una cadena de estilo responsabilidad (tenga en cuenta que el envío polimórfica también trabaja como esto - de la clase más derivada hacia arriba):
public class Composition : DynamicObject {
private List<object> targets = new List<object>();
public Composition(params object[] targets) {
AddTargets(targets);
}
protected void AddTargets(IEnumerable<object> targets) {
this.targets.AddRange(targets);
}
public override bool TryInvokeMember(
InvokeMemberBinder binder, object[] args, out object result) {
foreach (var target in targets) {
var methods = target.GetType().GetMethods();
var targetMethod = methods.FirstOrDefault(m =>
m.Name == binder.Name && ParametersMatch(m, args));
if (targetMethod != null) {
result = targetMethod.Invoke(target, args);
return true;
}
}
return base.TryInvokeMember(binder, args, out result);
}
private bool ParametersMatch(MethodInfo method, object[] args) {
var typesAreTheSame = method.GetParameters().Zip(
args,
(param, arg) => param.GetType() == arg.GetType());
return typesAreTheSame.Count() == args.Length &&
typesAreTheSame.All(_=>_);
}
}
en cuenta que también querría poner en práctica la delegación de propiedades (TryGetMember
y TrySetMember
), indexadores (TryGetIndex
y TrySetIndex
) y operadores (TryBinaryOperation
y TryUnaryOperation
).
Entonces, dado un conjunto de clases:
class MyClass {
public void MyClassMethod() {
Console.WriteLine("MyClass::Method");
}
}
class MyOtherClass {
public void MyOtherClassMethod() {
Console.WriteLine("MyOtherClass::Method");
}
}
Puede "mezcla" todos ellos juntos:
dynamic blend = new Composition(new MyClass(), new MyOtherClass());
blend.MyClassMethod();
blend.MyOtherClassMethod();
Puede también ampliar el objeto dinámico utilizar atributos clases u otros tipos de anotaciones para buscar mixins. Por ejemplo, dada esta interfaz anotación:
public interface Uses<M> where M : new() { }
puede tener este DynamicObject
:
public class MixinComposition : Composition {
public MixinComposition(object target) :
base(target) {
AddTargets(ResolveMixins(target.GetType()));
}
private IEnumerable<object> ResolveMixins(Type mainType) {
return ResolveMixinTypes(mainType).
Select(m => InstantiateMixin(m));
}
private IEnumerable<Type> ResolveMixinTypes(Type mainType) {
return mainType.GetInterfaces().
Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(Uses<>)).
Select(u => u.GetGenericArguments()[0]);
}
private object InstantiateMixin(Type type) {
return Activator.CreateInstance(type);
}
}
y crear su "mezclas" de esta manera:
class MyMixin {
public void MyMixinMethod() {
Console.WriteLine("MyMixin::Method");
}
}
class MyClass : Uses<MyMixin> {
public void MyClassMethod() {
Console.WriteLine("MyClass::Method");
}
}
...
dynamic blend = new MixinComposition(new MyClass());
blend.MyClassMethod();
blend.MyMixinMethod();
¿Parcial classes quizás? ¿Métodos de extensión? –
Al usar métodos de extensión, estaría escribiendo un código más explícito que case MyClass con mi código relacionado con el etiquetado (además del atributo de etiqueta). Me gustaría hacer esto sin casarme explícitamente con los dos. – ActionJackson