【问题标题】:Mixins with C# 4.0与 C# 4.0 的混合
【发布时间】:2011-10-02 10:43:35
【问题描述】:

我看到了各种关于是否可以在 C# 中创建 mixin 的问题,并且它们通常被定向到 codeplex 上的 re-mix 项目。但是,我不知道我是否喜欢“完整界面”的概念。理想情况下,我会像这样扩展一个类:

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

通过简单地添加 Taggable 接口,我可以通过某种对象工厂创建 MyClass 类型的对象。返回的实例将具有 MyClass 中定义的所有成员以及通过添加标记属性(如标记集合)提供的所有成员。使用 C# 4.0(动态关键字)似乎很容易做到这一点。重新混合项目使用 C# 3.5。有没有人有任何通过 C# 4.0 扩展对象而不改变类本身的好方法?谢谢。

【问题讨论】:

  • 部分课程?扩展方法?
  • 通过使用扩展方法,我将编写更明确的代码,将 MyClass 与我的标记相关代码结合起来(当然除了 taggable 属性之外)。我想在不明确将两者结合的情况下这样做。

标签: c#-4.0 mixins


【解决方案1】:

您可以在 C# 4.0 中创建类似 mixin 的构造,而无需使用动态,在接口上使用扩展方法和 ConditionalWeakTable 类来存储状态。看看here 的想法。

这是一个例子:

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。您可以使用标记界面来做到这一点。

【讨论】:

  • 天哪,这太棒了。票数怎么这么少?没有多重继承的语言使得编写 mixin 变得如此困难。这是 .NET 语言和 Java 的一大缺点。非常好的博文!
  • 该方法很好,但不允许来自 mixin 的方法的多态变化。因此,您的其他答案要强大得多(有趣的是,您可以添加两个答案...)
【解决方案2】:

您可以创建一个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();

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

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 序列化器可以处理 mixin 吗?
  • 有些人在遇到问题时会想“我知道,我会使用动态类型”。现在他们动态地重新调度问题并转换为解决方案,并达到了编程的必杀技。
【解决方案3】:

我一直致力于 C# pMixins 的开源 Mixin 框架。它利用部分类和代码生成器将 Mixin 类连接到 Target:

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

【讨论】:

【解决方案4】:

我知道这是一个老话题,但我还想介绍一个我目前正在从事的开源项目: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

【讨论】:

    【解决方案5】:

    我在 2008 年使用依赖注入样式库参与了一个项目,该库允许我们使用内部领域特定语言 (DSL) 定义应用程序的设计(在代码中)。

    该库让我们定义系统并从其他系统组合这些系统。一个系统表示一组在一个范围内实现接口的对象。系统/子系统可以选择向父作用域公开接口。

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

    您也许也可以使用现代依赖注入框架来做到这一点。

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

    注意:我在 2008 年写过 NDI。

    【讨论】:

      猜你喜欢
      • 2013-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-22
      • 1970-01-01
      相关资源
      最近更新 更多