web-dev-qa-db-ja.com

C#4.0のミックスイン

ミックスインをC#で作成できるかどうかについてさまざまな質問を目にしましたが、それらはコードプレックスのリミックスプロジェクトに向けられることがよくあります。しかし、「完全なインターフェース」の概念が好きかどうかはわかりません。理想的には、次のようにクラスを拡張します。

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

Taggableインターフェースを追加するだけで、ある種のオブジェクトファクトリを介してMyClass型のオブジェクトを作成できます。返されるインスタンスには、MyClassで定義されたすべてのメンバーと、タグ付け属性(タグのコレクションなど)を追加することによって提供されたすべてのメンバーが含まれます。これは、C#4.0(動的キーワード)を使用して簡単に実行できるようです。リミックスプロジェクトはC#3.5を使用します。クラス自体を変更せずにC#4.0を介してオブジェクトを拡張する良い方法はありますか?ありがとう。

30
ActionJackson

C#4.0では、動的を使用せずにミックスインのような構造を作成できます。インターフェイスの拡張メソッドと、状態を格納するための ConditionalWeakTable クラスを使用します。アイデアについては ここ を見てください。

次に例を示します。

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

属性を使用する際の問題は、クラスからミックスインにジェネリックパラメーターをフローできないことです。これは、マーカーインターフェイスを使用して行うことができます。

68
Jordão

受信した呼び出しを一連の責任スタイルでターゲットのリストに転送する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(_=>_);
  }

}

プロパティ(TryGetMemberおよびTrySetMember)、インデクサー(TryGetIndexおよびTrySetIndex)、および演算子(TryBinaryOperationおよびTryUnaryOperation)。

次に、一連のクラスが与えられます。

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();
18
Jordão

私は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();
   }
}
3
Philip Pittle

これが古いトピックであることは知っていますが、現在取り組んでいるオープンソースプロジェクトも紹介したいと思います: mixinSharp

これは、必要な委任コードを生成することでC#にミックスインサポートを追加する、Visual Studio2015用のRoslynベースのリファクタリング拡張機能です。

たとえば、再利用したい次のミックスインコードがあるとします。

// mixin class with the code you want to reuse
public class NameMixin
{
    public string Name { get; set; }
    public void DoSomething() { }
}

そして、ミックスインを含めたい特定の子クラス:

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

NameMixin _nameフィールドでmixinSharpリファクタリングステップを実行すると、拡張機能は、クラスにミックスインを含めるために必要なすべてのグルーコードを自動的に追加します。

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には、ミックスインインスタンスのコンストラクタインジェクション、ミックスインを使用したインターフェイスの実装など、いくつかの追加機能があります。

ソースは github で入手でき、バイナリ(コンパイルされたVisual Studio拡張機能)は Visual Studio Gallery で入手できます。

3
pgenfer

私は2008年に、内部ドメイン固有言語(DSL)を使用してアプリケーションの設計を(コードで)定義できる依存性注入スタイルライブラリを使用してプロジェクトに取り組みました。

ライブラリを使用すると、システムを定義し、他のシステムからそれらのシステムを構成できます。システムは、スコープ内にインターフェイスを実装するオブジェクトのセットを表しました。システム/サブシステムは、インターフェイスを親スコープに公開することを選択できます。

これの効果は、ミックスインが無料で来たということでした。動作のスライスを実装するクラスをシステム定義に追加し、そのインターフェイスを親スコープに公開するだけです。そのシステムは今その振る舞いをしています。

最新の依存性注入フレームワークでもこれを実行できる可能性があります。

NDIを使用していました( https://github.com/NigelThorne/ndependencyinjection/wiki )。

注:私は2008年にNDIを作成しました。

0
Nigel Thorne