代码之家  ›  专栏  ›  技术社区  ›  Stewart Johnson

是否可以在c_中实现mixins?

  •  53
  • Stewart Johnson  · 技术社区  · 16 年前

    我听说用扩展方法是可能的,但我自己也不太明白。如果可能的话,我想看一个具体的例子。

    谢谢!

    9 回复  |  直到 6 年前
        1
  •  86
  •   Jon Skeet    16 年前

    这真的取决于你所说的“混音”是什么意思——每个人似乎都有一个稍微不同的想法。我喜欢的那种混音 喜欢 要查看(但在c中不可用)正在使通过组合实现变得简单:

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

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

    但目前这一切都不可能:)

        2
  •  9
  •   erisco    12 年前

    有一个开放源码框架,使您能够通过c_实现mixin。看一看 http://remix.codeplex.com/ .

    用这个框架实现mixin非常容易。只需看看样本和页面上给出的“附加信息”链接。

        3
  •  7
  •   Community CDub    8 年前

    我通常采用这种模式:

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

    我在同一个源文件/命名空间中有两个定义。 这样,当使用接口(带有“using”)时,扩展总是可用的。

    这给了你一个 有限混合 如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);
        }
    }
    

    尽管jon提出的编译器技巧会更好。

        4
  •  4
  •   Community CDub    11 年前

    LinFu Castle's DynamicProxy 实施混音。cop(composite-oriented programming)可以被认为是用mixin构建了一个完整的范例。 This post from Anders Noras 有有用的信息和链接。

    编辑:这在C 2.0中是完全可能的,没有扩展方法

        5
  •  3
  •   staafl    10 年前

    您还可以扩展扩展方法来合并状态,模式与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实现来避免由于集合将目标类引用作为键而导致的内存泄漏。

        6
  •  2
  •   mll5    10 年前

    我需要类似的东西,所以我想出了以下使用反射。发射。在下面的代码中,将动态生成一个新类型,该类型具有“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);
    
        7
  •  1
  •   regisbsb    10 年前

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

        8
  •  0
  •   Tunaki    9 年前

    这是我刚刚想到的一个mixin实现。我可能会用它 a library of mine .

    可能是以前在什么地方做的。

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

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

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

    public interface Mixin {}
    

    这个接口是由mixins实现的。混音是普通班。类型不直接继承或实现mixin。相反,它们只是使用接口公开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没有检测到扩展方法 混合 适用于 Test ,但编译器确实接受它,只要 试验 实际上有混音。如果你尝试,

    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中,提醒您它的存在,和b)提供一个更具描述性的错误消息(由 Obsolete 属性)。

        9
  •  0
  •   BartoszKP    6 年前

    我找到了一个解决办法 here ,虽然不是完全优雅,但允许您实现完全可观察的mixin行为。另外,智能感知仍然有效!

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