2008-11-01 6 views

ответ

87

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

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

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

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

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

+0

Прохладная техника ... Я бы определенно проголосовал за эту технику. – 2010-02-10 08:46:20

+11

Андерс, пожалуйста, добавьте это на C# 5! – Schneider 2010-08-26 05:51:19

+48

Я нахожу это раздражающим, что эксперты C++ делают такие утверждения, как «Предпочитают композицию для наследования», но язык (C++ или C#) предлагает очень мало полезной для «правильной вещи». – 2010-09-27 14:10:05

4

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

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

7

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

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

5

Я обычно использую эту модель:

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

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

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

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

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

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

Было бы хорошо, если бы они (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); 
    } 
} 

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

3

Вы также можете расширить подход метода расширения, чтобы включить состояние в шаблон, отличный от приложенных свойств 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

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

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

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

Так что если у вас есть класс, который вы можете наследовать от «ObjectBase» и украсить IHasStuff вы можете добавить только шляпы Теперь

0

Вот реализация Mixin я просто придумали. Я, вероятно, буду использовать его с a library of mine.

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

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

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

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

public interface Mixin {} 

Этот интерфейс реализован mixins. Микшины - это обычные классы. Типы не наследуют или не реализуют микшины напрямую. Вместо этого они просто выставляют экземпляр 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).