2011-07-11 21 views
26

He visto varias preguntas con respecto a si mixins se pueden crear en C# y a menudo se dirigen al proyecto de remezcla en codeplex. Sin embargo, no sé si me gusta el concepto de "interfaz completa". Idealmente, me gustaría extender una clase de esta manera:Mixins con C# 4.0

[Taggable] 
    public class MyClass 
    { 
     .... 
    } 

Simplemente añadiendo la interfaz taggable, puedo crear objetos de tipo MiClase a través de algún tipo de generador de objetos. La instancia devuelta tendrá todos los miembros definidos en MyClass, así como todos los miembros proporcionados al agregar el atributo de etiquetado (como una colección de etiquetas). Parece que esto sería fácilmente factible usando C# 4.0 (la palabra clave dinámica). El proyecto de remezcla usa C# 3.5. ¿Alguien tiene alguna buena forma de extender objetos a través de C# 4.0 sin alterar las clases? Gracias.

+0

¿Parcial classes quizás? ¿Métodos de extensión? –

+0

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

Respuesta

65

Puede crear construcciones de tipo Mixin en C# 4.0 sin usar dinámica, con métodos de extensión en interfaces y la clase ConditionalWeakTable para almacenar el estado. Eche un vistazo here para la idea.

He aquí un ejemplo:

public interface MNamed { 
    // required members go here 
} 
public static class MNamedCode { 
    // provided methods go here, as extension methods to MNamed 

    // to maintain state: 
    private class State { 
    // public fields or properties for the desired state 
    public string Name; 
    } 
    private static readonly ConditionalWeakTable<MNamed, State> 
    _stateTable = new ConditionalWeakTable<MNamed, State>(); 

    // to access the state: 
    public static string GetName(this MNamed self) { 
    return _stateTable.GetOrCreateValue(self).Name; 
    } 
    public static void SetName(this MNamed self, string value) { 
    _stateTable.GetOrCreateValue(self).Name = value; 
    } 
} 

utilizar de esta manera:

class Order : MNamed { // you can list other mixins here... 
    ... 
} 

... 

var o = new Order(); 
o.SetName("My awesome order"); 

... 

var name = o.GetName(); 

El problema de usar un atributo es que no puede fluir a parámetros genéricos de la clase a la mixin. Puedes hacer esto con interfaces de marcador.

+8

Santa mierda esto es increíble. ¿Cómo tiene tan pocos votos? Un lenguaje sin herencia múltiple hace que escribir mixins sea tan difícil. Este es un gran inconveniente de los lenguajes .NET y Java. Muy buena publicación de blog! – kevinarpe

+3

El enfoque es bueno, pero no permite la variación polimórfica de los métodos de una mixins. Su otra respuesta es mucho más poderosa (es gracioso que pueda agregar dos respuestas ...) –

17

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(); 
+0

Buena idea. ¿Crees que los serializadores XML y JSON podrían hacer frente a los mixins? –

+0

Algunas personas, cuando se enfrentan con un problema, piensan "Lo sé, usaré la escritura dinámica". Ahora ellos redistribuyen dinámicamente el Problema y lo convierten en Solución y han logrado la programación del nirvana. – tjbtech

3

He estado trabajando en un marco de Mixin de código abierto para C# pMixins.Aprovecha las clases parciales y generadores de código a cable en la clase Mixin en el destino:

//Mixin - Class that contains members that should be injected into other classes. 
public class Mixin 
{ 
    // This method should be in several class 
    public void Method(){ } 
} 

//Target (Note: That it is partial) - Add members from Mixin 
[pMixn(Target = typeof(Mixin)] 
public partial class Target{} 


//Example of using Target 
public class Consumer 
{ 
    public void Example() 
    { 
     var target = new Target(); 

     // can call mixed in method 
     target.Method(); 

     // can implicitly convert Target to Mixin 
     Mixin m = new Target(); 
     m.Method(); 
    } 
} 
+2

+1 marco interesante. También comencé [algo similar] (https://code.google.com/p/nroles/) hace algún tiempo. –

3

Sé que esto es un viejo tema, pero también me gustaría presentar un proyecto de código abierto Actualmente estoy trabajando en: mixinSharp .

Es una extensión de refactorización basada en Roslyn para Visual Studio 2015 que agrega soporte de mixin a C# al generar el código de delegación requerido.

Por ejemplo, digamos que usted tiene el siguiente código de mixin desea volver a utilizar:

// mixin class with the code you want to reuse 
public class NameMixin 
{ 
    public string Name { get; set; } 
    public void DoSomething() { } 
} 

Y la clase niño dado en el que desea incluir su mixin:

// child class where the mixin should be included 
public class Person 
{ 
    // reference to the mixin 
    private NameMixin _name = new NameMixin(); 
} 

Si ejecuta el paso de refactorización de mixinSharp en el campo NameMixin _name, la extensión agregará automáticamente todo el código de pegamento que se requiere para incluir el mixin en su clase:

public class Person 
{ 
    // reference to the mixin 
    private NameMixin _name = new NameMixin(); 

    public string Name 
    { 
     get { return _name.Name; } 
     set { _name.Name = value; } 
    } 
    public void DoSomething() => _name.DoSomething(); 
} 

Además de esto, mixinSharp tiene algunas características adicionales como la inyección de constructor para instancias mixin, la implementación de interfaces con mixins y más.

Las fuentes están disponibles en github y los archivos binarios (la extensión compilada de Visual Studio) están disponibles en el Visual Studio Gallery.