是否可以在 C# 中实现 mixin?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/255553/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is it possible to implement mixins in C#?
提问by Stewart Johnson
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!
谢谢!
回答by Jon Skeet
It really depends on what you mean by "mixin" - everyone seems to have a slightly different idea. The kind of mixin I'd liketo see (but which isn't available in C#) is making implementation-through-composition simple:
这真的取决于你所说的“mixin”是什么意思——每个人的想法似乎都略有不同。我想那种混入的喜欢看(但不是在C#有售)正在实施,通过构图简单:
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.
除非类中直接有另一个实现,否则编译器将通过将每个成员代理到“impl”来实现 ISomeInterface。
None of this is possible at the moment though :)
不过目前这一切都不可能:)
回答by Mauricio Scheffer
LinFuand Castle's DynamicProxyimplement mixins. COP (Composite Oriented Programming) could be considered as making a whole paradigm out of mixins. This post from Anders Norashas useful informations and links.
LinFu和Castle 的DynamicProxy实现了 mixins。COP(面向复合编程)可以被认为是从 mixin 中创建一个完整的范式。安德斯·诺拉斯 (Anders Noras) 的这篇文章提供了有用的信息和链接。
EDIT: This is all possible with C# 2.0, without extension methods
编辑:这在 C# 2.0 中都是可能的,没有扩展方法
回答by Stefan Papp
There is an open source framework that enables you to implement mixins via C#. Have a look on http://remix.codeplex.com/.
有一个开源框架可以让您通过 C# 实现 mixin。看看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.
使用这个框架很容易实现mixin。只需查看页面上提供的示例和“其他信息”链接即可。
回答by 3dGrabber
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 mixinas described in CMS' first link.
这为您提供了有限的 mixin,如 CMS 的第一个链接中所述。
Limitations:
限制:
- no data fields
- no properties (you'll have to call myColor.Luminance() with parentheses, extension propertiesanyone?)
- 没有数据字段
- 没有属性(你必须用括号调用 myColor.Luminance(),任何人都必须调用扩展属性?)
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:
如果他们 (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);
}
}
Although Jon's proposed compiler trick would be even nicer.
尽管 Jon 提出的编译器技巧会更好。
回答by staafl
You could also augment the extension method approach to incorporate state, in a pattern not unlike WPF's attached properties.
您还可以在与 WPF 的附加属性不同的模式中扩充扩展方法方法以合并状态。
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.
请注意,它也适用于 null 实例,因为扩展方法自然会这样做。
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.
您还可以考虑使用 WeakDictionary 实现来避免由于集合将目标类引用作为键而导致的内存泄漏。
回答by mll5
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:
我需要类似的东西,所以我想出了以下使用 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();
// }
}
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);
回答by regisbsb
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.
如果你有一个可以存储数据的基类,你可以强制编译器安全并使用标记接口。这或多或少是已接受答案中的“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);
}
}
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
因此,如果您有一个类,您可以从“ObjectBase”继承并使用 IHasStuff 进行装饰,您现在可以添加 sutff
回答by GregRos
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.
它都是静态类型的,没有字典或其他东西。每个类型需要一点额外的代码,每个实例不需要任何存储。另一方面,如果您愿意,它还为您提供了动态更改 mixin 实现的灵活性。没有后期构建、预构建、中期构建工具。
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:
这个接口是由mixin实现的。Mixin 是常规类。类型不直接继承或实现混入。相反,它们只是使用接口公开一个 mixin 实例:
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.
实现这个接口意味着支持mixin。明确地实现它很重要,因为我们将为每种类型提供几个。
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:
Mixout
公开适当类型的混合。现在,为了测试这一点,让我们定义:
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,
有趣的是(尽管回想起来,它确实有意义),IntelliSense 没有检测到扩展方法Mixout
适用于Test
,但编译器确实接受它,只要Test
确实有 mixin。如果你试试,
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).
它的作用是,a) 显示一个Mixout
在 IntelliSense 中调用的方法,提醒您它的存在,以及 b) 提供更具描述性的错误消息(由Obsolete
属性生成)。
回答by BartoszKP
I've found a workaround here, which while not entirely elegant, allows you to achieve fully observable mixin behavior. Additionally, IntelliSense still works!
我在这里找到了一种解决方法,虽然它并不完全优雅,但可以让您实现完全可观察的混合行为。此外,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();
}
}