2008-11-01 17 views

Respuesta

87

Realmente depende de lo que quiere decir con "mixin": todos parecen tener una idea ligeramente diferente. El tipo de mixin que había como para ver (pero que no está disponible en C#) está haciendo la implementación a través de composición sencilla:

public class Mixin : ISomeInterface 
{ 
    private SomeImplementation impl implements ISomeInterface; 

    public void OneMethod() 
    { 
     // Specialise just this method 
    } 
} 

el compilador aplicar ISomeInterface con sólo hacer proxy a todos los miembros a " impl "a menos que haya otra implementación en la clase directamente.

Nada de esto es posible por el momento aunque :)

+0

Técnica genial ... Definitivamente votaría por esta técnica. –

+11

Anders por favor agregue esto a C# 5 !! – Schneider

+48

Me resulta molesto que los expertos en C++ hagan afirmaciones como "Prefieren la composición a la herencia", pero el lenguaje (C++ o C#) ofrece muy poca ayuda para hacer lo "correcto". –

4

LinFu y Castle's DynamicProxy implemento mixins. COP (Programación Orientado a Compuestos) podría considerarse como un paradigma completo de mixins. This post from Anders Noras tiene información útil y enlaces.

EDIT: Todo esto es posible con C# 2.0, y sin métodos de extensión

7

Existe un marco de código abierto que permite implementar mixins a través de C#. Eche un vistazo al http://remix.codeplex.com/.

Es muy fácil implementar mixins con este marco. Solo eche un vistazo a los ejemplos y a los enlaces de "Información adicional" que se brindan en la página.

+0

remix.codeplex.com acaba de ser publicado –

+4

¡Utilicé mi poder de reputación para arreglar el enlace! – erisco

5

que suelo emplear este patrón:

public interface IColor 
{ 
    byte Red {get;} 
    byte Green {get;} 
    byte Blue {get;} 
} 

public static class ColorExtensions 
{ 
    public static byte Luminance(this IColor c) 
    { 
     return (byte)(c.Red*0.3 + c.Green*0.59+ c.Blue*0.11); 
    } 
} 

tengo las dos definiciones en el mismo archivo de origen/espacio de nombres. De esta forma, las extensiones siempre están disponibles cuando se usa la interfaz (con 'using').

Esto le da un limited mixin como se describe en el primer enlace de CMS.

Limitaciones:

  • no hay campos de datos
  • no hay propiedades (que tendrá que llamar myColor.Luminance() con paréntesis, extension properties alguien?)

es todavía suficiente para muchos situaciones

Sería bueno si ellos (MS) podría añadir un poco de magia compilador para generar automáticamente la clase de extensión:

public interface IColor 
{ 
    byte Red {get;} 
    byte Green {get;} 
    byte Blue {get;} 

    // compiler generates anonymous extension class 
    public static byte Luminance(this IColor c)  
    { 
     return (byte)(c.Red*0.3 + c.Green*0.59+ c.Blue*0.11); 
    } 
} 

Aunque propuesto truco compilador de Jon sería aún mejor.

+0

Yo uso lo mismo. Métodos de extensión en interfaces. Veo esto desde wikpedia en mixins en C#. "... Los métodos de extensión proporcionan funcionalidad adicional en una clase existente sin modificar la clase. Luego es posible crear una clase auxiliar estática para la funcionalidad específica que define los métodos de extensión ..." – Gregor

3

También podría aumentar el enfoque del método de extensión para incorporar el estado, en un patrón similar a las propiedades adjuntas de WPF.

Aquí hay un ejemplo con un texto estándar mínimo.Tenga en cuenta que no se requieren modificaciones en las clases de destino, incluida la adición de interfaces, a menos que necesite tratar políticamente la clase de destino, en cuyo caso termina con algo muy cercano a la Herencia Múltiple real.

// Mixin class: mixin infrastructure and mixin component definitions 
public static class Mixin 
{ 
    // ===================================== 
    // ComponentFoo: Sample mixin component 
    // ===================================== 

    // ComponentFooState: ComponentFoo contents 
    class ComponentFooState 
    { 
     public ComponentFooState() { 
      // initialize as you like 
      this.Name = "default name"; 
     } 

     public string Name { get; set; } 
    } 

    // ComponentFoo methods 

    // if you like, replace T with some interface 
    // implemented by your target class(es) 

    public static void 
    SetName<T>(this T obj, string name) { 
     var state = GetState(component_foo_states, obj); 

     // do something with "obj" and "state" 
     // for example: 

     state.Name = name + " the " + obj.GetType(); 


    } 
    public static string 
    GetName<T>(this T obj) { 
     var state = GetState(component_foo_states, obj); 

     return state.Name; 
    } 

    // ===================================== 
    // boilerplate 
    // ===================================== 

    // instances of ComponentFoo's state container class, 
    // indexed by target object 
    static readonly Dictionary<object, ComponentFooState> 
    component_foo_states = new Dictionary<object, ComponentFooState>(); 

    // get a target class object's associated state 
    // note lazy instantiation 
    static TState 
    GetState<TState>(Dictionary<object, TState> dict, object obj) 
    where TState : new() { 
     TState ret; 
     if(!dict.TryGet(obj, out ret)) 
      dict[obj] = ret = new TState(); 

     return ret; 
    } 

} 

Uso:

var some_obj = new SomeClass(); 
some_obj.SetName("Johny"); 
Console.WriteLine(some_obj.GetName()); // "Johny the SomeClass" 

Tenga en cuenta que también trabaja con casos nulos, ya que los métodos de extensión hacen naturalmente.

También puede considerar el uso de una implementación WeakDictionary para evitar las pérdidas de memoria causadas por el mantenimiento de la colección a las referencias de clase de destino como claves.

+1

La firma de GetState solo tiene un tipo Arg, pero las llamadas a GetState por GetName y SetName pasan en dos. ¿Cómo se supone que esto realmente funciona? –

+0

@Thick_propheT, tienes razón, por supuesto. Hice algunas simplificaciones después de la publicación original, pero parece haber perdido este bit. ¡Gracias! – staafl

2

Necesitaba algo similar, así que se me ocurrió lo siguiente con Reflection.Emit. En el siguiente código, se genera dinámicamente un nuevo tipo que tiene un miembro privado del tipo 'mixin'. Todas las llamadas a los métodos de la interfaz 'mixin' se envían a este miembro privado. Se define un único constructor de parámetros que toma una instancia que implementa la interfaz 'mixin'. Básicamente, es igual a escribir el siguiente código para un tipo concreto dada T y la interfaz I:

class Z : T, I 
{ 
    I impl; 

    public Z(I impl) 
    { 
     this.impl = impl; 
    } 

    // Implement all methods of I by proxying them through this.impl 
    // as follows: 
    // 
    // I.Foo() 
    // { 
    // return this.impl.Foo(); 
    // } 
} 

Esta es la clase:

public class MixinGenerator 
{ 
    public static Type CreateMixin(Type @base, Type mixin) 
    { 
     // Mixin must be an interface 
     if (!mixin.IsInterface) 
      throw new ArgumentException("mixin not an interface"); 

     TypeBuilder typeBuilder = DefineType(@base, new Type[]{mixin}); 

     FieldBuilder fb = typeBuilder.DefineField("impl", mixin, FieldAttributes.Private); 

     DefineConstructor(typeBuilder, fb); 

     DefineInterfaceMethods(typeBuilder, mixin, fb); 

     Type t = typeBuilder.CreateType(); 

     return t; 
    } 

    static AssemblyBuilder assemblyBuilder; 
    private static TypeBuilder DefineType(Type @base, Type [] interfaces) 
    { 
     assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
      new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.RunAndSave); 

     ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(Guid.NewGuid().ToString()); 

     TypeBuilder b = moduleBuilder.DefineType(Guid.NewGuid().ToString(), 
      @base.Attributes, 
      @base, 
      interfaces); 

     return b; 
    } 
    private static void DefineConstructor(TypeBuilder typeBuilder, FieldBuilder fieldBuilder) 
    { 
     ConstructorBuilder ctor = typeBuilder.DefineConstructor(
      MethodAttributes.Public, CallingConventions.Standard, new Type[] { fieldBuilder.FieldType }); 

     ILGenerator il = ctor.GetILGenerator(); 

     // Call base constructor 
     ConstructorInfo baseCtorInfo = typeBuilder.BaseType.GetConstructor(new Type[]{}); 
     il.Emit(OpCodes.Ldarg_0); 
     il.Emit(OpCodes.Call, typeBuilder.BaseType.GetConstructor(new Type[0])); 

     // Store type parameter in private field 
     il.Emit(OpCodes.Ldarg_0); 
     il.Emit(OpCodes.Ldarg_1); 
     il.Emit(OpCodes.Stfld, fieldBuilder); 
     il.Emit(OpCodes.Ret); 
    } 

    private static void DefineInterfaceMethods(TypeBuilder typeBuilder, Type mixin, FieldInfo instanceField) 
    { 
     MethodInfo[] methods = mixin.GetMethods(); 

     foreach (MethodInfo method in methods) 
     { 
      MethodInfo fwdMethod = instanceField.FieldType.GetMethod(method.Name, 
       method.GetParameters().Select((pi) => { return pi.ParameterType; }).ToArray<Type>()); 

      MethodBuilder methodBuilder = typeBuilder.DefineMethod(
              fwdMethod.Name, 
              // Could not call absract method, so remove flag 
              fwdMethod.Attributes & (~MethodAttributes.Abstract), 
              fwdMethod.ReturnType, 
              fwdMethod.GetParameters().Select(p => p.ParameterType).ToArray()); 

      methodBuilder.SetReturnType(method.ReturnType); 
      typeBuilder.DefineMethodOverride(methodBuilder, method); 

      // Emit method body 
      ILGenerator il = methodBuilder.GetILGenerator(); 
      il.Emit(OpCodes.Ldarg_0); 
      il.Emit(OpCodes.Ldfld, instanceField); 

      // Call with same parameters 
      for (int i = 0; i < method.GetParameters().Length; i++) 
      { 
       il.Emit(OpCodes.Ldarg, i + 1); 
      } 
      il.Emit(OpCodes.Call, fwdMethod); 
      il.Emit(OpCodes.Ret); 
     } 
    } 
} 

Este es el uso:

public interface ISum 
{ 
    int Sum(int x, int y); 
} 

public class SumImpl : ISum 
{ 
    public int Sum(int x, int y) 
    { 
     return x + y; 
    } 
} 

public class Multiply 
{   
    public int Mul(int x, int y) 
    { 
     return x * y; 
    } 
} 

// Generate a type that does multiply and sum 
Type newType = MixinGenerator.CreateMixin(typeof(Multiply), typeof(ISum)); 

object instance = Activator.CreateInstance(newType, new object[] { new SumImpl() }); 

int res = ((Multiply)instance).Mul(2, 4); 
Console.WriteLine(res); 
res = ((ISum)instance).Sum(1, 4); 
Console.WriteLine(res); 
+0

Algunas personas, cuando se enfrentan con un problema, piensan "Lo sé, usaré Reflection.Emit". Ahora vuelven a compilar el problema para incluir los códigos de operación de la solución y han logrado programar el nirvana. – tjbtech

1

Si tiene una clase base que puede almacenar datos, puede aplicar la seguridad del compilador y usar interfaces de marcadores. Eso es más o menos lo que "Mixins in C# 3.0" propone la respuesta aceptada.

public static class ModelBaseMixins 
{ 
    public interface IHasStuff{ } 

    public static void AddStuff<TObjectBase>(this TObjectBase objectBase, Stuff stuff) where TObjectBase: ObjectBase, IHasStuff 
    { 
     var stuffStore = objectBase.Get<IList<Stuff>>("stuffStore"); 
     stuffStore.Add(stuff); 
    } 
} 

El ObjectBase:

public abstract class ObjectBase 
{ 
    protected ModelBase() 
    { 
     _objects = new Dictionary<string, object>(); 
    } 

    private readonly Dictionary<string, object> _objects; 

    internal void Add<T>(T thing, string name) 
    { 
     _objects[name] = thing; 
    } 

    internal T Get<T>(string name) 
    { 
     T thing = null; 
     _objects.TryGetValue(name, out thing); 

     return (T) thing; 
    } 

Así que si usted tiene una clase puede heredar de 'ObjectBase' y decorar con IHasStuff puede agregar sutff ahora

0

Aquí es una aplicación mixin I' solo se me ocurrió. Probablemente lo use con a library of mine.

Probablemente se haya hecho antes, en alguna parte.

Todo está escrito estáticamente, sin diccionarios o algo así. Requiere un poco de código adicional por tipo, no necesita ningún almacenamiento por instancia. Por otro lado, también le ofrece la flexibilidad de cambiar la implementación de mixin sobre la marcha, si así lo desea. No hay herramientas de construcción posterior a la compilación, precompilación ni construcción.

Tiene algunas limitaciones, pero permite cosas como anulación, etc.

Comenzamos definiendo una interfaz de marcador. Tal vez se agregará algo más adelante:

public interface Mixin {} 

Esta interfaz está implementada por mixins. Mixins son clases regulares. Los tipos no heredan ni implementan mixins directamente. En su lugar, solo exponen una instancia de la mezcla utilizando la interfaz:

public interface HasMixins {} 

public interface Has<TMixin> : HasMixins 
    where TMixin : Mixin { 
    TMixin Mixin { get; } 
} 

La implementación de esta interfaz significa soportar la mezcla.Es importante que se implemente explícitamente, ya que vamos a tener varios de estos por tipo.

Ahora para un pequeño truco usando métodos de extensión. Definimos:

public static class MixinUtils { 
    public static TMixin Mixout<TMixin>(this Has<TMixin> what) 
     where TMixin : Mixin { 
     return what.Mixin; 
    } 
} 

Mixout expone el mixin del tipo apropiado. Ahora, para probar esto, vamos a definir:

public abstract class Mixin1 : Mixin {} 

public abstract class Mixin2 : Mixin {} 

public abstract class Mixin3 : Mixin {} 

public class Test : Has<Mixin1>, Has<Mixin2> { 

    private class Mixin1Impl : Mixin1 { 
     public static readonly Mixin1Impl Instance = new Mixin1Impl(); 
    } 

    private class Mixin2Impl : Mixin2 { 
     public static readonly Mixin2Impl Instance = new Mixin2Impl(); 
    } 

    Mixin1 Has<Mixin1>.Mixin => Mixin1Impl.Instance; 

    Mixin2 Has<Mixin2>.Mixin => Mixin2Impl.Instance; 
} 

static class TestThis { 
    public static void run() { 
     var t = new Test(); 
     var a = t.Mixout<Mixin1>(); 
     var b = t.Mixout<Mixin2>(); 
    } 
} 

Más bien divertida (aunque en retrospectiva, que tiene sentido), IntelliSense no detecta que el método de extensión Mixout aplica a Test, pero el compilador lo acepta, siempre y cuando Test tenga realmente la mezcla. Si lo intenta,

t.Mixout<Mixin3>(); 

Le da un error de compilación.

Puede ir un poco extravagante, y definir el método siguiente también:

[Obsolete("The object does not have this mixin.", true)] 
public static TSome Mixout<TSome>(this HasMixins something) where TSome : Mixin { 
    return default(TSome); 
} 

Lo que esto hace es, a) mostrar un método llamado Mixout en IntelliSense, que le recuerda de su existencia, y b) proporcionar un mensaje de error algo más descriptivo (generado por el atributo Obsolete).

Cuestiones relacionadas