C# 4.0 中的混合类

31

我看到了一些关于在C#中是否可以创建mixin的问题,他们经常被引导到Codeplex上的re-mix项目。然而,我不确定我是否喜欢“完整接口”这个概念。理想情况下,我会像这样扩展一个类:

    [Taggable]
    public class MyClass
    {
       ....
    }

只需添加Taggable接口,我就可以通过某种对象工厂创建MyClass类型的对象。返回的实例将具有MyClass中定义的所有成员以及通过添加标记属性(例如标记集合)提供的所有成员。使用C# 4.0(动态关键字)似乎很容易实现这一点。remix项目使用C# 3.5。有没有人知道如何在不修改类本身的情况下通过C# 4.0扩展对象的好方法?谢谢。


部分类也许可以解决这个问题。扩展方法呢? - George Johnston
通过使用扩展方法,我可以编写更明确的代码来将我的 MyClass 与我的标记相关代码(当然还要加上 taggable 属性)结合起来。我想这样做的同时又不需要明确地把它们结合起来。 - ActionJackson
5个回答

73

在C# 4.0中,您可以使用接口上的扩展方法和ConditionalWeakTable类来存储状态,而无需使用动态方式创建类似于mixin的结构。点击此处查看该想法。

以下是示例:

public interface MNamed { 
  // required members go here
}
public static class MNamedCode {
  // provided methods go here, as extension methods to MNamed

  // to maintain state:
  private class State { 
    // public fields or properties for the desired state
    public string Name;
  }
  private static readonly ConditionalWeakTable<MNamed, State>
    _stateTable = new ConditionalWeakTable<MNamed, State>();

  // to access the state:
  public static string GetName(this MNamed self) {
    return _stateTable.GetOrCreateValue(self).Name;
  }
  public static void SetName(this MNamed self, string value) {
    _stateTable.GetOrCreateValue(self).Name = value;
  }
}

像这样使用:

class Order : MNamed { // you can list other mixins here...
  ...
}

...

var o = new Order();
o.SetName("My awesome order");

...

var name = o.GetName();

使用属性的问题在于无法将类中的泛型参数传递到mixin。而使用标记接口可以解决这个问题。


9
天啊,这太棒了。为什么票数这么少?没有多继承的语言使编写混合组件非常困难。这是.NET语言和Java的一个重大缺点。非常好的博客文章! - kevinarpe
3
这种方法不错,但不允许混入中的方法进行多态变化。因此,你的另一个答案更加强大(有趣的是你可以添加两个答案...) - Johannes Rudolph

19

您可以创建一个DynamicObject,将其接收到的调用转发到一个目标列表中,在责任链样式中(注意多态分派也是这样工作的 - 从最具体的类向上):

public class Composition : DynamicObject {
  private List<object> targets = new List<object>();

  public Composition(params object[] targets) {
    AddTargets(targets);
  }

  protected void AddTargets(IEnumerable<object> targets) {
    this.targets.AddRange(targets);
  }

  public override bool TryInvokeMember(
        InvokeMemberBinder binder, object[] args, out object result) {
    foreach (var target in targets) {
      var methods = target.GetType().GetMethods();
      var targetMethod = methods.FirstOrDefault(m => 
        m.Name == binder.Name && ParametersMatch(m, args));
      if (targetMethod != null) {
        result = targetMethod.Invoke(target, args);
        return true;
      }
    }
    return base.TryInvokeMember(binder, args, out result);
  }

  private bool ParametersMatch(MethodInfo method, object[] args) {
    var typesAreTheSame = method.GetParameters().Zip(
      args, 
      (param, arg) => param.GetType() == arg.GetType());
    return typesAreTheSame.Count() == args.Length && 
            typesAreTheSame.All(_=>_);
  }

}

请注意,您还需要实现属性(TryGetMemberTrySetMember)、索引器(TryGetIndexTrySetIndex)和操作符(TryBinaryOperationTryUnaryOperation)的委托。

然后,给定一组类:

class MyClass {
  public void MyClassMethod() {
    Console.WriteLine("MyClass::Method");
  }
}

class MyOtherClass {
  public void MyOtherClassMethod() {
    Console.WriteLine("MyOtherClass::Method");
  }
}
你可以将它们全部"混合"在一起:
dynamic blend = new Composition(new MyClass(), new MyOtherClass());
blend.MyClassMethod();
blend.MyOtherClassMethod();

您还可以扩展动态对象,使用类属性或其他类型的注释来查找mixin。例如,给定此注释接口:

public interface Uses<M> where M : new() { }
你可以拥有这个DynamicObject:
public class MixinComposition : Composition {

  public MixinComposition(object target) : 
    base(target) { 
    AddTargets(ResolveMixins(target.GetType()));
  }

  private IEnumerable<object> ResolveMixins(Type mainType) {
    return ResolveMixinTypes(mainType).
      Select(m => InstantiateMixin(m));
  }

  private IEnumerable<Type> ResolveMixinTypes(Type mainType) {
    return mainType.GetInterfaces().
      Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(Uses<>)).
      Select(u => u.GetGenericArguments()[0]);
  }

  private object InstantiateMixin(Type type) {
    return Activator.CreateInstance(type);
  }

}

就像这样创建你的“混合物”:

class MyMixin {
  public void MyMixinMethod() {
    Console.WriteLine("MyMixin::Method");
  }
}

class MyClass : Uses<MyMixin> {
  public void MyClassMethod() {
    Console.WriteLine("MyClass::Method");
  }
}

...

dynamic blend = new MixinComposition(new MyClass());
blend.MyClassMethod();
blend.MyMixinMethod();

好主意。您认为XML和JSON序列化程序能处理Mixins吗? - Richard Schneider
有些人遇到问题时会想:“我知道了,我会使用动态类型。” 现在他们将问题重新分派为Solution并进行强制转换,已经达到了编程的涅磐。 - tjbtech

3
我知道这是一个老话题,但我也想介绍一个我目前正在开发的开源项目:mixinSharp
它是一个基于Roslyn的重构扩展,适用于Visual Studio 2015,通过生成所需的委托代码为C#添加mixin支持。
例如,假设您有以下要重用的mixin代码:
// mixin class with the code you want to reuse
public class NameMixin
{
    public string Name { get; set; }
    public void DoSomething() { }
}

你需要包含mixin的子类:


// child class where the mixin should be included
public class Person
{
    // reference to the mixin
    private NameMixin _name = new NameMixin();
}

如果你在NameMixin _name字段上执行mixinSharp重构步骤,该扩展将自动添加所有所需的粘合代码,以将mixin包含在你的类中:
public class Person
{
  // reference to the mixin
  private NameMixin _name = new NameMixin();

  public string Name
  {
      get { return _name.Name; }
      set { _name.Name = value; }
  }
  public void DoSomething() => _name.DoSomething();
}

除此之外,mixinSharp 还具有一些额外的功能,例如为 mixin 实例进行构造函数注入、使用 mixin 实现接口等等。
源代码可在 github 上获得,而二进制文件(已编译的 Visual Studio 扩展)则可在 Visual Studio Gallery 中获取。

3

我一直在为C#工作的开源Mixin框架pMixins努力。它利用部分类和代码生成器将Mixin类连接到目标中:

//Mixin - Class that contains members that should be injected into other classes.
public class Mixin
{
   // This method should be in several class
   public void Method(){ }
}

//Target (Note: That it is partial) - Add members from Mixin
[pMixn(Target = typeof(Mixin)]
public partial class Target{}


//Example of using Target
public class Consumer
{
    public void Example()
    {
        var target = new Target();

        // can call mixed in method
        target.Method();

        // can implicitly convert Target to Mixin
        Mixin m = new Target();
        m.Method();
   }
}

2
+1 有趣的框架。我也在一段时间前开始了类似的项目(https://code.google.com/p/nroles/)。 - Jordão

0

我在2008年参与了一个项目,使用了一种依赖注入风格的库,让我们可以使用内部领域特定语言(DSL)在代码中定义应用程序的设计。

该库允许我们定义系统,并从其他系统组合这些系统。系统代表了在范围内实现接口的对象集合。系统/子系统可以选择向父范围公开接口。

这样做的效果是mixin功能是免费的。您只需将实现行为片段的类添加到系统定义中,并将其接口公开给父范围。该系统现在具有该行为。

现代依赖注入框架也可能能够实现这一点。

我们当时使用的是NDI(https://github.com/NigelThorne/ndependencyinjection/wiki)。

注意:我在2008年编写了NDI。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接