Возможно ли реализовать миксины в C #? - PullRequest
54 голосов
/ 01 ноября 2008

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

Спасибо!

Ответы [ 9 ]

88 голосов
/ 01 ноября 2008

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

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

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

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

Пока это невозможно, хотя:)

10 голосов
/ 12 января 2011

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

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

7 голосов
/ 13 октября 2011

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

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) могли добавить магию компилятора для автоматической генерации класса расширения:

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

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

4 голосов
/ 01 ноября 2008

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

РЕДАКТИРОВАТЬ: Это все возможно с C # 2.0, без методов расширения

3 голосов
/ 03 августа 2012

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

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

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

}

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

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

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

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

2 голосов
/ 22 февраля 2015

Мне нужно что-то подобное, поэтому я придумал следующее, используя 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();
    // }
}

Это класс:

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

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

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);
1 голос
/ 28 мая 2015

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

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

База объектов:

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

Итак, если у вас есть класс, который вы можете наследовать от ObjectBase и украшать его с помощью IHasStuff, вы можете добавить sutff сейчас

0 голосов
/ 04 октября 2018

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

using System;
using System.Runtime.CompilerServices; //needed for ConditionalWeakTable
public interface MAgeProvider // use 'M' prefix to indicate mixin interface
{
    // nothing needed in here, it's just a 'marker' interface
}
public static class AgeProvider // implements the mixin using extensions methods
{
    static ConditionalWeakTable<MAgeProvider, Fields> table;
    static AgeProvider()
    {
        table = new ConditionalWeakTable<MAgeProvider, Fields>();
    }
    private sealed class Fields // mixin's fields held in private nested class
    {
        internal DateTime BirthDate = DateTime.UtcNow;
    }
    public static int GetAge(this MAgeProvider map)
    {
        DateTime dtNow = DateTime.UtcNow;
        DateTime dtBorn = table.GetOrCreateValue(map).BirthDate;
        int age = ((dtNow.Year - dtBorn.Year) * 372
                   + (dtNow.Month - dtBorn.Month) * 31
                   + (dtNow.Day - dtBorn.Day)) / 372;
        return age;
    }
    public static void SetBirthDate(this MAgeProvider map, DateTime birthDate)
    {
        table.GetOrCreateValue(map).BirthDate = birthDate;
    }
}

public abstract class Animal
{
    // contents unimportant
}
public class Human : Animal, MAgeProvider
{
    public string Name;
    public Human(string name)
    {
        Name = name;
    }
    // nothing needed in here to implement MAgeProvider
}
static class Test
{
    static void Main()
    {
        Human h = new Human("Jim");
        h.SetBirthDate(new DateTime(1980, 1, 1));
        Console.WriteLine("Name {0}, Age = {1}", h.Name, h.GetAge());
        Human h2 = new Human("Fred");
        h2.SetBirthDate(new DateTime(1960, 6, 1));
        Console.WriteLine("Name {0}, Age = {1}", h2.Name, h2.GetAge());
        Console.ReadKey();
    }
}
0 голосов
/ 28 марта 2016

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

Возможно, это было сделано где-то раньше.

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

Он имеет некоторые ограничения, но допускает такие вещи, как переопределение и т. Д.

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

public interface Mixin {}

Этот интерфейс реализован миксинами. Миксины - это регулярные занятия. Типы не наследуют и не реализуют миксины напрямую. Вместо этого они просто выставляют экземпляр миксина, используя интерфейс:

public interface HasMixins {}

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

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

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

public static class MixinUtils {
    public static TMixin Mixout<TMixin>(this Has<TMixin> 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<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>();
    }
}

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

t.Mixout<Mixin3>();

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

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

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

Что это делает, а) отображает метод с именем Mixout в IntelliSense, напоминая вам о его существовании, и б) предоставляет несколько более описательное сообщение об ошибке (генерируемое атрибутом Obsolete).

...