我听说有可能使用扩展方法,但我自己也无法弄明白。如果可能的话,我想看一个具体的例子。
谢谢!
答案 0 :(得分:88)
这实际上取决于你对“mixin”的意思 - 每个人似乎都有一个略微不同的想法。我想要看的混合类型(但是在C#中不可用)正在通过组合实现简单:
public class Mixin : ISomeInterface
{
private SomeImplementation impl implements ISomeInterface;
public void OneMethod()
{
// Specialise just this method
}
}
只要将每个成员代理为“impl”,编译器就会实现ISomeInterface,除非在类中直接有另一个实现。
目前这一切都不可能:)
答案 1 :(得分:9)
有一个开源框架,可以让你通过C#实现mixins。看看http://remix.codeplex.com/。
使用此框架实现mixins非常容易。只需查看样本和页面上的“附加信息”链接即可。
答案 2 :(得分:7)
我通常采用这种模式:
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')。
这会为您提供有限的mixin ,如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提出的编译技巧会更好。
答案 3 :(得分:4)
LinFu和Castle's DynamicProxy实现了mixins。 COP(面向复合的编程)可以被认为是用mixins制作一个完整的范例。 This post from Anders Noras包含有用的信息和链接。
编辑:这可以通过C#2.0实现,无需扩展方法
答案 4 :(得分:3)
您还可以使用与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实现来避免由于集合将目标类引用保留为键而导致的内存泄漏。
答案 5 :(得分:2)
我需要类似的东西,所以我使用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);
答案 6 :(得分:1)
如果您有可以存储数据的基类,则可以强制执行编译器安全性并使用标记接口。 这或多或少是什么&#34; Mixins in C#3.0&#34;从接受的答案提出。
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;
}
因此,如果您有一个类,您可以继承自&#39; ObjectBase&#39;并用IHasStuff装饰你现在可以添加sutff
答案 7 :(得分:0)
这是我刚刚提出的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
,但编译器确实接受它,只要{{1实际上有mixin。如果你试试,
Test
它会给你一个编译错误。
你可以有点花哨,并定义以下方法:
t.Mixout<Mixin3>();
这样做是,a)在IntelliSense中显示一个名为[Obsolete("The object does not have this mixin.", true)]
public static TSome Mixout<TSome>(this HasMixins something) where TSome : Mixin {
return default(TSome);
}
的方法,提醒您它的存在,以及b)提供一个更具描述性的错误消息(由Mixout
属性生成)。
答案 8 :(得分:0)
我找到了一种解决方法here,尽管这种解决方法并不十分优雅,但可以使您实现完全可观察的混合行为。此外,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();
}
}