Возможно ли реализовать mixins в C #?

Я слышал, что это возможно с помощью методов расширения, но я не могу понять это сам. Я хотел бы увидеть конкретный пример, если это возможно.

Благодаря!

    Это действительно зависит от того, что вы подразумеваете под «mixin» – у каждого, кажется, есть немного другая идея. Тип mixin, который я хотел бы видеть (но который недоступен на C #), делает простоту реализации:

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

    Компилятор будет реализовывать ISomeInterface, просто проксируя каждого члена в «impl», если только в нем не будет другой реализации.

    На данный момент ничто из этого не возможно, хотя 🙂

    Существует среда с открытым исходным кодом, которая позволяет вам реализовать mixins через C #. Посмотрите на http://remix.codeplex.com/ .

    Очень просто реализовать mixins с этой структурой. Просто просмотрите образцы и ссылки «Дополнительная информация», приведенные на странице.

    Обычно я использую этот шаблон:

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

    У меня есть два определения в том же исходном файле / пространстве имен. Таким образом, расширения всегда доступны, когда используется интерфейс (с «использованием»).

    Это дает вам ограниченный микс, как описано в первой ссылке CMS.

    Ограничения:

    • нет полей данных
    • нет свойств (вам нужно будет вызвать myColor.Luminance () с круглыми скобками, какие-либо свойства расширения ?)

    Этого достаточно для многих ситуаций.

    Было бы неплохо, если бы они (MS) могли добавить некоторую магию компилятора для автоматического генерации classа расширения:

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

    Хотя предложенный Джоном компилятор трюк будет еще приятнее.

    LinFu и DynamicProxy реализовывают миксины. COP (композитно-ориентированное программирование) можно рассматривать как создание целой парадигмы из миксинов. Этот пост от Андерса Нораса имеет полезную информацию и ссылки.

    EDIT: все это возможно с C # 2.0, без методов расширения

    Вы также можете расширить подход метода расширения для включения состояния в шаблон, отличный от приложенных свойств WPF.

    Вот пример с минимальным шаблоном. Обратите внимание, что никаких изменений не требуется для целевых classов, включая добавление интерфейсов, если только вам не нужно иметь дело с целевым classом полиморфно – в этом случае вы получите нечто очень близкое к фактическому множественному наследованию.

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

    Применение:

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

    Обратите внимание, что он также работает с нулевыми экземплярами, поскольку естественным образом используются методы расширения.

    Вы также можете рассмотреть возможность использования реализации WeakDictionary, чтобы избежать утечек памяти, вызванных тем, что коллекция удерживает целевые ссылки на classы как ключи.

    Мне было нужно что-то подобное, поэтому я придумал следующее с помощью Reflection.Emit. В следующем коде динамически создается новый тип, который имеет частный член типа ‘mixin’. Все вызовы методам интерфейса mixin передаются этому частному участнику. Определен единственный конструктор параметров, который принимает экземпляр, который реализует интерфейс «mixin». В принципе, он равен написанию следующего кода для конкретного конкретного типа T и интерфейса 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(); // } } 

    Это class:

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

    Это использование:

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

    Если у вас есть базовый class, который может хранить данные, вы можете обеспечить безопасность компилятора и использовать интерфейсы маркеров. Это более или менее то, что предлагает «Mixins в C # 3.0» из принятого ответа.

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

    Объектная база:

     public abstract class ObjectBase { protected ModelBase() { _objects = new Dictionary(); } private readonly Dictionary _objects; internal void Add(T thing, string name) { _objects[name] = thing; } internal T Get(string name) { T thing = null; _objects.TryGetValue(name, out thing); return (T) thing; } 

    Итак, если у вас есть class, который вы можете наследовать от «ObjectBase» и украсить IHasStuff, вы можете добавить sutff сейчас

    Вот реализация mixin, которую я только что придумал. Я, вероятно, буду использовать его с моей библиотекой .

    Скорее всего, это было где-то раньше.

    Все это статически типизировано, без словарей или чего-то еще. Для этого требуется немного дополнительного кода для каждого типа, вам не нужно какое-либо хранилище на один экземпляр. С другой стороны, это также дает вам гибкость в изменении реализации mixin «на лету», если вы этого желаете. Нет пост-сборки, предварительная assembly, инструменты средней сборки.

    У этого есть некоторые ограничения, но это позволяет вещи, такие как переопределение и так далее.

    Начнем с определения интерфейса маркера. Возможно, что-то будет добавлено к нему позже:

     public interface Mixin {} 

    Этот интерфейс реализуется mixins. Микшины – это обычные classы. Типы не наследуют или не реализуют микшины напрямую. Вместо этого они просто выставляют экземпляр mixin с помощью интерфейса:

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

    Реализация этого интерфейса означает поддержку микширования. Важно, что он реализован явно, так как у нас будет несколько из них для каждого типа.

    Теперь для небольшого трюка с использованием методов расширения. Мы определяем:

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

    Mixout соответствующего типа. Теперь, чтобы проверить это, давайте определим:

     public abstract class Mixin1 : Mixin {} public abstract class Mixin2 : Mixin {} public abstract class Mixin3 : Mixin {} public class Test : Has, Has { private class Mixin1Impl : Mixin1 { public static readonly Mixin1Impl Instance = new Mixin1Impl(); } private class Mixin2Impl : Mixin2 { public static readonly Mixin2Impl Instance = new Mixin2Impl(); } Mixin1 Has.Mixin => Mixin1Impl.Instance; Mixin2 Has.Mixin => Mixin2Impl.Instance; } static class TestThis { public static void run() { var t = new Test(); var a = t.Mixout(); var b = t.Mixout(); } } 

    Скорее забавно (хотя в ретроспективе это имеет смысл), IntelliSense не обнаруживает, что метод расширения Mixout применяется к Test , но компилятор действительно принимает его, пока Test самом деле имеет mixin. Если вы пытаетесь,

     t.Mixout(); 

    Это дает вам ошибку компиляции.

    Вы можете немного притворяться и определить следующий метод:

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

    Это означает, что a) отображает метод, называемый Mixout в IntelliSense, напоминающий вам о его существовании, и b) предоставляет несколько более описательное сообщение об ошибке (генерируемое атрибутом Obsolete ).

    Давайте будем гением компьютера.