Я слышал, что это возможно с помощью методов расширения, но я не могу понять это сам. Я бы хотел увидеть конкретный пример, если это возможно.
Спасибо!
Я слышал, что это возможно с помощью методов расширения, но я не могу понять это сам. Я бы хотел увидеть конкретный пример, если это возможно.
Спасибо!
Это действительно зависит от того, что вы подразумеваете под "mixin" - у всех, кажется, есть немного другая идея. Тип mixin, который я хотел бы видеть (но который недоступен на С#), делает простую реализацию реализации:
public class Mixin : ISomeInterface
{
private SomeImplementation impl implements ISomeInterface;
public void OneMethod()
{
// Specialise just this method
}
}
Компилятор будет реализовывать ISomeInterface, просто проксируя каждого члена в "impl", если только в нем не было другой реализации.
В настоящий момент это невозможно, хотя:)
Существует среда с открытым исходным кодом, которая позволяет вам реализовать mixins через С#. Посмотрите 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.
Ограничения:
Это все еще достаточно для многих ситуаций.
Было бы неплохо, если бы они (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);
}
}
Хотя Джон предложил компилятор трюк будет еще лучше.
LinFu и Замок DynamicProxy реализует Примеси. COP (композитно-ориентированное программирование) можно рассматривать как создание целой парадигмы из миксинов. Этот пост от Anders Noras содержит полезную информацию и ссылки.
EDIT: все это возможно с С# 2.0 без методов расширения
Вы также можете расширить подход метода расширения для включения состояния в шаблон, отличный от приложенных свойств 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, чтобы избежать утечек памяти, вызванных захватом коллекции для целевых ссылок на классы как ключи.
Мне нужно что-то подобное, поэтому я придумал следующее с помощью 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 в С# 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
Вот реализация 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
).