【问题标题】:Is it possible to implement mixins in C#?是否可以在 C# 中实现 mixins?
【发布时间】:2010-09-20 07:28:48
【问题描述】:

我听说可以使用扩展方法,但我自己不太清楚。如果可能的话,我想看一个具体的例子。

谢谢!

【问题讨论】:

    标签: c# extension-methods mixins


    【解决方案1】:

    这真的取决于你所说的“mixin”是什么意思——每个人的想法似乎都略有不同。我希望喜欢看到的那种 mixin(但在 C# 中不可用)使实现通过组合变得简单:

    public class Mixin : ISomeInterface
    {
        private SomeImplementation impl implements ISomeInterface;
    
        public void OneMethod()
        {
            // Specialise just this method
        }
    }
    

    编译器将通过将每个成员代理到“impl”来实现 ISomeInterface,除非类中直接有另一个实现。

    目前这些都不可能:)

    【讨论】:

    • Anders 请将其添加到 C# 5 中!!
    • 我发现 C++ 专家发表“更喜欢组合而不是继承”之类的声明很烦人,但该语言(C++ 或 C#)在做“正确的事情”方面几乎没有提供宝贵的帮助。
    • 刚看到connect open on this on the VS Feedback rss feed。喜欢就去投票吧。
    • 我已经开始implementing roles in C#。看一看。他们可以实现接口并将这些实现带到组合类中。
    • @JonSkeet:我不是反对者,但由于这个问题需要一个具体的例子,我希望反对者不知道所提供的答案是“这不是很好吗?”而不是目前实用的解决方案。
    【解决方案2】:

    我通常采用这种模式:

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

    虽然 Jon 提出的编译器技巧会更好。

    【讨论】:

    • 我用的是同一个。接口上的扩展方法。我在 c# 中的 mixins 上从维基百科上看到了这一点。 “...扩展方法在现有类上提供附加功能,而无需修改该类。然后可以为定义扩展方法的特定功能创建一个静态帮助器类......”
    • 尽管这样做有风险,但这可能是唯一的方法,无需下载任何第三方的东西,就可以做 mixin 应该做的事情——保持独立,同时允许它们有选择地合并在一起运行。由于这可能存在风险的原因,一方面,很容易意外覆盖底层对象的现有方法
    【解决方案3】:

    有一个开源框架使您能够通过 C# 实现 mixins。看看http://remix.codeplex.com/

    用这个框架很容易实现 mixins。只需查看示例和页面上提供的“附加信息”链接即可。

    【讨论】:

      【解决方案4】:

      LinFuCastle's DynamicProxy 实现混合。 COP(面向复合编程)可以被认为是用 mixins 制作一个完整的范例。 This post from Anders Noras 有有用的信息和链接。

      编辑:这在 C# 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'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"
        

        请注意,它也适用于空实例,因为扩展方法自然会这样做。

        您还可以考虑使用 Wea​​kDictionary 实现来避免由于集合将目标类引用作为键而导致的内存泄漏。

        【讨论】:

        • GetState的签名只有一个Type Arg,但是GetName和SetName对GetState的调用传入了两个。这真的应该如何工作?
        • @Thick_propheT,你当然是对的。我在原始帖子之后做了一些简化,但似乎错过了这一点。谢谢!
        【解决方案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);
        

        【讨论】:

        • 有些人在遇到问题时会想“我知道,我会使用 Reflection.Emit”。现在他们重新编译问题以包含解决方案操作码并达到编程必杀技。
        【解决方案7】:

        我找到了一种解决方法here,虽然不是很优雅,但可以让您实现完全可观察的 mixin 行为。此外,IntelliSense 仍然有效!

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

        【讨论】:

          【解决方案8】:

          如果您有一个可以存储数据的基类,您可以强制编译器安全并使用标记接口。 这或多或少是公认答案中的“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);
              }
          }
          

          对象库:

          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

          【讨论】:

            【解决方案9】:

            这是我刚刚提出的一个 mixin 实现。我可能会将它与a library of mine 一起使用。

            这可能以前在某个地方做过。

            都是静态类型的,没有字典什么的。每种类型都需要一些额外的代码,每个实例不需要任何存储。另一方面,如果您愿意,它还为您提供了动态更改 mixin 实现的灵活性。没有后期构建、预构建、中期构建工具。

            它有一些限制,但它确实允许覆盖等操作。

            我们首先定义一个标记接口。也许稍后会添加一些东西:

            public interface Mixin {}
            

            这个接口是由 mixins 实现的。 Mixins 是常规类。类型不直接继承或实现 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 公开了适当类型的 mixin。现在,为了测试这一点,让我们定义:

            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 属性生成)。

            【讨论】:

              猜你喜欢
              • 2011-10-16
              • 1970-01-01
              • 2011-07-09
              • 1970-01-01
              • 2020-01-07
              • 2012-12-29
              • 1970-01-01
              • 2021-04-08
              • 2015-03-28
              相关资源
              最近更新 更多