Is it possible to implement mixins in C#?
我听说使用扩展方法是可能的,但我自己也不太明白。如果可能的话,我想看看具体的例子。
谢谢!
这真的取决于你所说的"混音"是什么意思——每个人似乎都有一个稍微不同的想法。我想看到的混合类型(但在C中没有)使通过组合实现变得简单:
1 2 3 4 5 6 7 8 9 | public class Mixin : ISomeInterface { private SomeImplementation impl implements ISomeInterface; public void OneMethod() { // Specialise just this method } } |
编译器将通过将每个成员代理为"impl"来实现isomeinterface,除非类中直接存在另一个实现。
但目前这一切都不可能:)
有一个开放源代码框架,使您能够通过C_实现混合。请访问http://remix.codeplex.com/。
用这个框架实现混合非常容易。只需查看一下示例和页面上提供的"附加信息"链接。
我通常采用这种模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(),扩展属性为anyone?)
在许多情况下,它仍然足够。
如果他们(ms)可以添加一些编译器魔术来自动生成扩展类,那就太好了:
1 2 3 4 5 6 7 8 9 10 11 12 | 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); } } |
尽管乔恩提出的编译器技巧会更好。
林福和城堡的动态氧气器混合。cop(面向复合的编程)可以被看作是从混合语言中创建一个完整的范例。这篇来自安德斯·诺拉斯的文章提供了有用的信息和链接。
编辑:这一切都可以用C 2.0实现,不需要扩展方法。
您还可以在一个与WPF的附加属性不同的模式中增加扩展方法来合并状态。
下面是一个具有最小样板的示例。注意,在目标类上不需要修改,包括添加接口,除非您需要以多态方式处理目标类——在这种情况下,您最终得到的是非常接近实际多重继承的东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | // 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; } } |
用途:
1 2 3 | 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编写以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); // } } |
这是课程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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); } } } |
这是用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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); |
如果您有一个可以存储数据的基类,那么您可以加强编译器的安全性并使用标记接口。这或多或少是"混入C 3.0"从公认的答案提出的。
1 2 3 4 5 6 7 8 9 10 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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"继承并用iHasTuff装饰,那么现在就可以添加sutff了。
我在这里找到了一个解决方法,虽然它并不完全优雅,但可以让你实现完全可见的混音行为。此外,IntelliSense仍然有效!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | 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(); } } |
这里是我刚想到的一个混合实现。我可能会和我的图书馆一起使用。
可能以前在某个地方做过。
它都是静态输入的,没有字典之类的东西。每种类型都需要一点额外的代码,每个实例不需要任何存储。另一方面,如果您愿意的话,它还为您提供了动态更改mixin实现的灵活性。没有后期生成、预生成和中期生成工具。
它有一些限制,但它允许覆盖等事情。
我们首先定义一个标记接口。也许稍后会添加一些内容:
1 | public interface Mixin {} |
这个接口是由mixins实现的。混音是常规课程。类型不直接继承或实现混合。相反,它们只是使用接口公开mixin的实例:
1 2 3 4 5 6 | public interface HasMixins {} public interface Has<TMixin> : HasMixins where TMixin : Mixin { TMixin Mixin { get; } } |
实现这个接口意味着支持mixin。它的显式实现很重要,因为每种类型都有几个这样的实现。
现在我们来看一下使用扩展方法的小技巧。我们定义:
1 2 3 4 5 6 | public static class MixinUtils { public static TMixin Mixout<TMixin>(this Has<TMixin> what) where TMixin : Mixin { return what.Mixin; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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没有检测到扩展方法
1 | t.Mixout<Mixin3>(); |
它会给您一个编译错误。
您可以稍微想象一下,并定义以下方法:
1 2 3 4 | [Obsolete("The object does not have this mixin.", true)] public static TSome Mixout<TSome>(this HasMixins something) where TSome : Mixin { return default(TSome); } |
这样做的目的是:a)在智能意义上显示一个名为