Подтвердить что ты не робот

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

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

Спасибо!

4b9b3361

Ответ 1

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

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

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

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

В настоящий момент это невозможно, хотя:)

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 5

Вы также можете расширить подход метода расширения для включения состояния в шаблон, отличный от приложенных свойств 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 state container class,
    //  indexed by target object
    static readonly Dictionary<object, ComponentFooState>
    component_foo_states = new Dictionary<object, ComponentFooState>();

    // get a target class object 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, чтобы избежать утечек памяти, вызванных захватом коллекции для целевых ссылок на классы как ключи.

Ответ 6

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

Ответ 7

Если у вас есть базовый класс, который может хранить данные, вы можете обеспечить безопасность компилятора и использовать маркерные интерфейсы. Это более или менее то, что предлагает "Mixins в С# 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

Ответ 8

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

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

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

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

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

public interface Mixin {}

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

public interface HasMixins {}

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

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

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

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

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