Я слышал, что это возможно с помощью методов расширения, но я не могу понять это сам. Я хотел бы увидеть конкретный пример, если это возможно.Возможно ли реализовать mixins в C#?
Спасибо!
Я слышал, что это возможно с помощью методов расширения, но я не могу понять это сам. Я хотел бы увидеть конкретный пример, если это возможно.Возможно ли реализовать mixins в C#?
Спасибо!
Это действительно зависит от того, что вы подразумеваете под «mixin» - у всех, кажется, есть немного другая идея. Вид Mixin я бы как видеть (но не доступен в C#) делает реализацию сквозной-композиционной простым:
public class Mixin : ISomeInterface
{
private SomeImplementation impl implements ISomeInterface;
public void OneMethod()
{
// Specialise just this method
}
}
компилятор будет осуществлять ISomeInterface только проксирующей каждый член в " impl ", если в классе не было другой реализации.
Все это возможно на данный момент, хотя :)
LinFu и Castle's DynamicProxy реализации Примеси. COP (композитно-ориентированное программирование) можно рассматривать как создание целой парадигмы из миксинов. This post from Anders Noras содержит полезную информацию и ссылки.
EDIT: Это все возможно с C# 2.0, без методов расширения
Существует фреймворк с открытым исходным кодом, который позволяет реализовать Примеси через 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);
}
}
У меня есть два определения в том же исходном файле/именах. Таким образом, расширения всегда доступны, когда используется интерфейс (с «использованием»).
Это дает вам ограниченный mixin, как описано в первой ссылке CMS.
Ограничения:
Это все еще достаточно для многих ситуации.
Было бы хорошо, если бы они (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);
}
}
Хотя предложенный компилятор трюк Джона был бы еще лучше.
Вы также можете расширить подход метода расширения, чтобы включить состояние в шаблон, отличный от приложенных свойств 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, чтобы избежать утечек памяти, вызванных хранением коллекции, для целевых ссылок на классы как ключи.
Мне было нужно нечто похожее, поэтому я придумал следующее с помощью 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);
Если у вас есть базовый класс, который может хранить данные, вы можете обеспечить безопасность компилятора и использовать маркерные интерфейсы. Это более или менее то, что предлагает «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 вы можете добавить только шляпы Теперь
Вот реализация 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
).
Прохладная техника ... Я бы определенно проголосовал за эту технику. – 2010-02-10 08:46:20
Андерс, пожалуйста, добавьте это на C# 5! – Schneider 2010-08-26 05:51:19
Я нахожу это раздражающим, что эксперты C++ делают такие утверждения, как «Предпочитают композицию для наследования», но язык (C++ или C#) предлагает очень мало полезной для «правильной вещи». – 2010-09-27 14:10:05