有可能在 C # 中实现混合吗?

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

谢谢!

36350 次浏览

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

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


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

除非类中直接有另一个实现,否则编译器将通过代理每个成员来实现 ISome Interface。

不过,眼下这一切都是不可能的:)

林富和 Castle 的 DynamicProxy实现混合。COP (组合面向编程)可以被认为是用 Mixin 创建了一个完整的范例。这篇文章来自 Anders Noras有有用的信息和链接。

编辑: 在没有扩展方法的情况下,使用 C # 2.0完全可以做到这一点

有一个开源框架可以让你通过 C # 实现混合。

用这个框架实现混合是非常容易的。只要看看样品和“附加信息”的网页上给出的链接。

我通常采用这种模式:

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 的第一个链接所描述的。

限制:

  • 没有数据字段
  • 没有属性(您必须使用括号调用 myColor. Luminance () ,扩展属性有人吗?)

对很多情况来说,这还是足够的。

如果他们(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 提出的编译技巧会更好。

您还可以扩展扩展方法来合并状态,其模式与 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 实例,因为扩展方法自然会这样做。

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

我需要一些类似的东西,所以我想出了以下使用反射。发射。在下面的代码中,将动态生成一个新类型,该类型有一个私有成员类型为‘ Mixin’。所有对‘ mix’接口方法的调用都转发给这个私有成员。定义了一个单参数构造函数,该构造函数接受一个实现“ 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 in 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);
}
}

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

下面是我刚刚想到的一个混合实现,我可能会在 我的一个图书馆中使用它。

可能以前在什么地方做过。

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

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

我们从定义一个标记接口开始,也许稍后会添加一些内容:

public interface Mixin {}

这个接口是由 Mixin 实现的。混合班是普通班。类型不直接继承或实现混合。相反,它们只是使用接口公开一个 mix 的实例:

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公开了适当类型的 mix。现在,为了测试这一点,让我们定义:

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实际上有混合语言。如果你尝试,

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

我已经找到了一个解决方案 给你,它虽然不完全优雅,但是允许您实现完全可观察的混合行为。此外,智能感知仍然可以工作!

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 在你的课堂上表现出来:

  • 使用扩展方法可以很容易地完成不保持任何状态的行为 Mixin
  • 使用 Duck 类型(当前为 IEnumerableIDisposable) 可以使用默认接口成员并显式实现所述接口的接口的行为 Mixin。然后,新的接口表现为一个 mix in,在这里可以添加和利用具体的行为,而不需要使用扩展方法,并且可以支持诸如 usingforeach之类的构造。
  • 通过使用扩展方法和静态 ConditionalWeakTable来保存数据,可以非常粗略地实现需要状态的混合。
  • 多重继承机制可以在编译时(T4,源生成器)或运行时(反射。发射)粗略地综合。