是否可以在 C# 中实现 mixin?

发布于 2024-07-07 12:16:35 字数 62 浏览 9 评论 0原文

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

谢谢!

I've heard that it's possible with extension methods, but I can't quite figure it out myself. I'd like to see a specific example if possible.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

热风软妹 2024-07-14 12:16:35

这实际上取决于“mixin”的含义 - 每个人似乎都有稍微不同的想法。 我希望看到的混合类型(但在 C# 中不可用)使组合实现变得简单:

public class Mixin : ISomeInterface
{
    private SomeImplementation impl implements ISomeInterface;

    public void OneMethod()
    {
        // Specialise just this method
    }
}

编译器只需将每个成员代理为“impl”即可实现 ISomeInterface除非类中直接有另一个实现。

不过目前这一切都是不可能的:)

It really depends on what you mean by "mixin" - everyone seems to have a slightly different idea. The kind of mixin I'd like to see (but which isn't available in C#) is making implementation-through-composition simple:

public class Mixin : ISomeInterface
{
    private SomeImplementation impl implements ISomeInterface;

    public void OneMethod()
    {
        // Specialise just this method
    }
}

The compiler would implement ISomeInterface just by proxying every member to "impl" unless there was another implementation in the class directly.

None of this is possible at the moment though :)

ι不睡觉的鱼゛ 2024-07-14 12:16:35

我通常采用这种模式:

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 提出的编译器技巧会更好。

I usually employ this pattern:

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

I have the two definitions in the same source file/namespace.
That way the extensions are always available when the interface is used (with 'using').

This gives you a limited mixin as described in CMS' first link.

Limitations:

  • no data fields
  • no properties (you'll have to call myColor.Luminance() with parentheses, extension properties anyone?)

It's still sufficient for many situations.

It would be nice if they (MS) could add some compiler magic to auto-generate the extension class:

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

Although Jon's proposed compiler trick would be even nicer.

飞烟轻若梦 2024-07-14 12:16:35

有一个开源框架可让您通过 C# 实现 mixin。 看看 http://remix.codeplex.com/

使用这个框架很容易实现 mixin。 只需查看示例和页面上给出的“其他信息”链接即可。

There is an open source framework that enables you to implement mixins via C#. Have a look on http://remix.codeplex.com/.

It is very easy to implement mixins with this framework. Just have a look on the samples and the "Additional Information" links given on the page.

戒ㄋ 2024-07-14 12:16:35

LinFuCastle 的 DynamicProxy 实现 mixins。 COP(面向复合编程)可以被认为是从 mixins 中构建出一个完整的范式。 Anders Noras 的这篇文章 包含有用的信息和链接。

编辑:这在 C# 2.0 中都是可能的,无需扩展方法

LinFu and Castle's DynamicProxy implement mixins. COP (Composite Oriented Programming) could be considered as making a whole paradigm out of mixins. This post from Anders Noras has useful informations and links.

EDIT: This is all possible with C# 2.0, without extension methods

初吻给了烟 2024-07-14 12:16:35

我需要类似的东西,所以我使用 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);

I needed something similar so I came up with the following using Reflection.Emit. In the following code a new type is dynamically generated which has a private member of type 'mixin'. All the calls to methods of 'mixin' interface are forwarded to this private member. A single parameter constructor is defined that takes an instance which implements the 'mixin' interface. Basically, it is equal to writing the following code for a given concrete type T and interface 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();
    // }
}

This is the class:

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

This is the usage:

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);
路还长,别太狂 2024-07-14 12:16:35

您还可以增强扩展方法来合并状态,其模式与 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"

请注意,它也适用于 null 实例,因为扩展方法自然也适用。

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

You could also augment the extension method approach to incorporate state, in a pattern not unlike WPF's attached properties.

Here is an example with minimum boilerplate. Note that no modification are required on the target classes, including adding interfaces, unless you need to deal with the target class polymorphically - in which case you end up with something very close to actual Multiple Inheritance.

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

}

Usage:

var some_obj = new SomeClass();
some_obj.SetName("Johny");
Console.WriteLine(some_obj.GetName()); // "Johny the SomeClass"

Note that it also works with null instances, since extension methods naturally do.

You might also consider using a WeakDictionary implementation to avoid memory leaks caused by the collection's holding on to target class references as keys.

老子叫无熙 2024-07-14 12:16:35

我找到了解决方法 这里,虽然不完全优雅,但允许您实现完全可观察的 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();
    }
}

I've found a workaround here, which while not entirely elegant, allows you to achieve fully observable mixin behavior. Additionally, IntelliSense still works!

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();
    }
}
南七夏 2024-07-14 12:16:35

如果您有一个可以存储数据的基类,您可以强制编译器安全并使用标记接口。
这或多或少是已接受答案中的“C# 3.0 中的 Mixins”所建议的。

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 进行装饰,您现在可以添加 sutff

If you have a base class that can store data you can enforce compiler safety and use marker interfaces.
That's more or less what "Mixins in C# 3.0" from the accepted answer proposes.

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

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

So if you have a Class you can inherit from 'ObjectBase' and decorate with IHasStuff you can add sutff now

萌酱 2024-07-14 12:16:35

这是我刚刚想出的一个 mixin 实现。 我可能会将它与我的库一起使用。

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

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

它有一些限制,但它确实允许诸如覆盖之类的事情。

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

public interface Mixin {}

这个接口是通过 mixins 实现的。 Mixin 是常规类。 类型不直接继承或实现 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 没有检测到扩展方法 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 属性生成)。

Here is a mixin implementation I've just come up with. I'll probably use it with a library of mine.

It's probably been done before, somewhere.

It's all statically typed, with no dictionaries or something. It requires a little bit of extra code per type, you don't need any storage per instance. On the other hand, it also gives you the flexibility of changing the mixin implementation on the fly, if you so desire. No post-build, pre-build, mid-build tools.

It has some limitations, but it does allow things like overriding and so on.

We begin by defining a marker interface. Perhaps something will be added to it later:

public interface Mixin {}

This interface is implemented by mixins. Mixins are regular classes. Types do not inherit or implement mixins directly. They instead just expose an instance of the mixin using the interface:

public interface HasMixins {}

public interface Has<TMixin> : HasMixins
    where TMixin : Mixin {
    TMixin Mixin { get; }
}

Implementing this interface means supporting the mixin. It's important that it's implemented explicitly, since we're going to have several of these per type.

Now for a little trick using extension methods. We define:

public static class MixinUtils {
    public static TMixin Mixout<TMixin>(this Has<TMixin> what)
        where TMixin : Mixin {
        return what.Mixin;
    }
}

Mixout exposes the mixin of the appropriate type. Now, to test this out, let's define:

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

Rather amusingly (though in retrospect, it does make sense), IntelliSense does not detect that the extension method Mixout applies to Test, but the compiler does accept it, as long as Test actually has the mixin. If you try,

t.Mixout<Mixin3>();

It gives you a compilation error.

You can go a bit fancy, and define the following method too:

[Obsolete("The object does not have this mixin.", true)]
public static TSome Mixout<TSome>(this HasMixins something) where TSome : Mixin {
    return default(TSome);
}

What this does is, a) display a method called Mixout in IntelliSense, reminding you of its existence, and b) provide a somewhat more descriptive error message (generated by the Obsolete attribute).

单调的奢华 2024-07-14 12:16:35

从根本上讲,有几种技术可以在类中获取 Mixin 行为:

  • 使用扩展方法可以轻松完成不保留任何状态的行为 mixins
  • 使用鸭子类型的接口的行为 mixins(当前为 IEnumerable 和 IEnumerable 和 IEnumerable ) IDisposable) 可以使用默认接口成员并显式实现所述接口。 然后,新接口的行为就像一个 mixin,可以在不使用扩展方法的情况下添加和利用具体行为,并且可以支持诸如 usingforeach 之类的结构。
  • 需要状态的 Mixin 可以通过使用扩展方法和静态 ConditionalWeakTable 来保存数据来非常粗略地实现。
  • 多重继承机制可以在编译时(T4,源生成器)或运行时(Reflection.Emit)粗略地综合。

There are, fundamentally, several techniques to getting Mixin behavior in your classes:

  • Behavioral mixins that hold no state are easily done using extension methods
  • Behavioral mixins for interfaces that use duck typing (currently, IEnumerable and IDisposable) can use default interface members with explicit implementation of said interface. Then, then new interface behaves as a mixin where concrete behavior can be added and leveraged without using extension methods, and can support constructs such as using and foreach.
  • Mixins that need state can be implemented very roughly by using extension methods and a static ConditionalWeakTable to hold data.
  • Multiple inheritance mechanics can be crudely synthesized at compile-time (T4, source generators) or runtime (Reflection.Emit).
心在旅行 2024-07-14 12:16:35

接口的默认实现几乎使混入成为可能,只要有一点创造力。

这是一个演示:

using System;

public class Program {
  public static void Main(string[] args) {
      var inst = new Baseline();
      Console.WriteLine("Baseline.Method() returns " + inst.Method());
      Console.WriteLine("Baseline.Method() returns " + inst.Method());
  }
}

public struct MixinImpl {
    public static MixinImpl Create() => new MixinImpl(); // Idiom to provide the mixin constructor
    private int counter;
    public int Method() => ++counter;
}

public interface Mixin {
    ref MixinImpl Impl { get; }
    int Method() => Impl.Method();
}

public class Baseline : Mixin {
    private MixinImpl mixinImpl = MixinImpl.Create();
    ref MixinImpl Mixin.Impl => ref mixinImpl;
}

因此,这是通过将 mixin 定义为 struct 和相应的 interface 来实现的; 其中接口包含 mixin 默认实现,而 struct 包含状态。 此实现的优点是,当您向 mixin 添加状态或方法时,不会破坏 ABI

唯一真正的缺点是无法进行 protected 访问限定符。 您唯一的保护选择是publicprivate。 您可以通过不提供转发器来进行假保护,但它实际上不起作用,因为一半的调用者将通过接口类型的变量,它可以看到 Impl 成员并访问结构体的成员。

这是隔离的 mixin 用户的开销,这样你就可以单独看到它:

: Mixin {
    private MixinImpl mixinImpl = MixinImpl.Create();
    ref MixinImpl Mixin.Impl => ref mixinImpl;

就是这样。 一个接口声明和两行代码。 第一行声明并创建 mixin 状态,第二行提供将所有内容连接在一起的粘合剂。 您可以拥有的 mixin 数量也没有限制。

mixin 提供程序的开销如下:

    int Method() => Impl.Method();

访问任何 private 内容的每个 public 属性和方法都需要一个从 interface的转发器>结构。

以这种方式完成的 Mixins 没有继承; 但是,您可以通过组合来伪造它并在派生的struct中实现基本接口。 接口转发提供了执行虚拟方法的能力。 这也意味着 mixins 可以有 mixins,但是如果你需要的话,你在做什么

道歉; TIO 的编译器已过时且不支持默认实现。

Default implementation of interfaces have pretty much enabled mixins to be possible with a bit of creativity.

Here's a demo:

using System;

public class Program {
  public static void Main(string[] args) {
      var inst = new Baseline();
      Console.WriteLine("Baseline.Method() returns " + inst.Method());
      Console.WriteLine("Baseline.Method() returns " + inst.Method());
  }
}

public struct MixinImpl {
    public static MixinImpl Create() => new MixinImpl(); // Idiom to provide the mixin constructor
    private int counter;
    public int Method() => ++counter;
}

public interface Mixin {
    ref MixinImpl Impl { get; }
    int Method() => Impl.Method();
}

public class Baseline : Mixin {
    private MixinImpl mixinImpl = MixinImpl.Create();
    ref MixinImpl Mixin.Impl => ref mixinImpl;
}

So this works by defining a mixin as a struct and a corresponding interface; where the interface contains the mixin default implementation and the struct contains the state. This implementation has the benefit of not breaking the ABI when you add either state or methods to the mixin.

The only real downside is there's no way to do protected access qualifier. Your only protection choices are public and private. You can kind of fake protected by not providing the forwarder but it doesn't really work because half of the callers will be via variables of the interface type, which can see the Impl member and access members of the struct.

Here's the overhead in the user of the mixin isolated so you can see it alone:

: Mixin {
    private MixinImpl mixinImpl = MixinImpl.Create();
    ref MixinImpl Mixin.Impl => ref mixinImpl;

That's it. One interface declaration and two lines of code. The first line declares and creates the mixin state and the second line provides the glue that ties everything together. There's no limit to the number of mixins you can have either.

The overhead on the mixin provider is as follows:

    int Method() => Impl.Method();

Every public property and method that accesses anything private needs a forwarder from the interface to the struct.

Mixins done this way don't have inheritance; however you can fake it with composition and implementing the base interface in the derived struct. The interface forwarding provides capacity to do virtual methods while you're at it. This also means mixins can have mixins, but what are you doing if you need that.

Apologies; TIO's compiler is out of date and doesn't support default implementations.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文