c# extension-methods mixins

¿Es posible implementar mixins en C#?



extension-methods (8)

LinFu y CastleProxy implementan mixins. COP (Programación Orientado a Compuestos) podría considerarse como un paradigma completo de mixins. Esta publicación de Anders Noras tiene información útil y enlaces.

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

He oído que es posible con los métodos de extensión, pero no puedo resolverlo por mi cuenta. Me gustaría ver un ejemplo específico si es posible.

¡Gracias!


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 funciona con instancias nulas, ya que los métodos de extensión naturalmente lo hacen.

También podría considerar usar una implementación de WeakDictionary para evitar las pérdidas de memoria causadas por el mantenimiento de la colección a las referencias de la clase objetivo como claves.


Existe un marco de código abierto que le permite implementar mixins a través de C #. Eche un vistazo en 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.


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

public class Mixin : ISomeInterface { private SomeImplementation impl implements ISomeInterface; public void OneMethod() { // Specialise just this method } }

El compilador implementaría ISomeInterface simplemente enviando a cada miembro a "impl" a menos que hubiera otra implementación en la clase directamente.

Nada de esto es posible en este momento :)


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 determinado tipo concreto T e 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);


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 en C # 3.0" de la respuesta aceptada propone.

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; }

Entonces, si tienes una clase que puedes heredar de ''ObjectBase'' y decorar con IHasStuff puedes agregar sutff ahora


Aquí hay una implementación de mixin que acabo de presentar. Probablemente lo use con una biblioteca mía .

Probablemente se haya hecho antes, en alguna parte.

Está todo tipado 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. Quizás algo se agregará más tarde:

public interface Mixin {}

Esta interfaz es 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; } }

Implementar esta interfaz significa soportar la mixin. 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 la Mixout del tipo apropiado. Ahora, para probar esto, definamos:

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>(); } }

De manera bastante divertida (aunque en retrospectiva, tiene sentido), IntelliSense no detecta que el método de extensión Mixout aplica a Test , pero el compilador lo acepta, siempre que Test tenga realmente la mezcla. Si intentas,

t.Mixout<Mixin3>();

Te da un error de compilación.

Puedes ir un poco elegante y definir el siguiente método 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 hace es, a) mostrar un método llamado Mixout en IntelliSense, que le recuerda su existencia, yb) proporcionar un mensaje de error algo más descriptivo (generado por el atributo Obsolete ).


Yo 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 fuente / espacio de nombres. De esta manera, las extensiones siempre están disponibles cuando se usa la interfaz (con ''using'').

Esto le da una mezcla limitada como se describe en el primer enlace de CMS.

Limitaciones

  • sin campos de datos
  • no hay propiedades (tendrá que llamar a myColor.Luminance () con paréntesis, ¿ propiedades de extensión ?)

Aún es suficiente para muchas situaciones.

Sería bueno si ellos (MS) pudieran agregar algo de magia del 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 el truco del compilador propuesto por Jon sería incluso mejor.