Mixins with C# 4.0

2019-01-21 10:54发布

I've seen various questions regarding if mixins can be created in C# and they are often directed to the re-mix project on codeplex. However, I don't know if I like the "complete interface" concept. Ideally, I would extend a class like so:

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

By simply adding the Taggable interface, I can create objects of type MyClass via some kind of object factory. The returned instance would have all the members defined in MyClass as well as all members provided by adding the tagging attribute (like a collection of tags). It seems like this would be easily doable using C# 4.0 (the dynamic keyword). The re-mix project uses C# 3.5. Does anyone have any good ways to extend objects via C# 4.0 without altering the classes themselves? Thanks.

标签: c#-4.0 mixins
5条回答
Emotional °昔
2楼-- · 2019-01-21 11:25

I know this is an old topic but I would also like to introduce an open source project I'm currently working on: mixinSharp.

It is a Roslyn based refactoring extension for Visual Studio 2015 which adds mixin support to C# by generating the required delegation code.

For example, let's say you have the following mixin code you want to reuse:

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

And the given child class where you want to include your mixin:

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

If you execute the mixinSharp refactoring step on the NameMixin _name field, the extension will automatically add all the glue code which is required to include the mixin in your class:

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

Besides this, mixinSharp has some additional features like constructor injection for mixin instances, implementing interfaces with mixins and more.

The sources are available at github and the binaries (the compiled Visual Studio extension) are available in the Visual Studio Gallery.

查看更多
beautiful°
3楼-- · 2019-01-21 11:26

You can create mixin-like constructs in C# 4.0 without using dynamic, with extension methods on interfaces and the ConditionalWeakTable class to store state. Take a look here for the idea.

Here's an example:

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;
  }
}

Use it like this:

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

...

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

...

var name = o.GetName();

The problem of using an attribute is that you can't flow generic parameters from the class to the mixin. You can do this with marker interfaces.

查看更多
爱情/是我丢掉的垃圾
4楼-- · 2019-01-21 11:30

I worked on a project in 2008 using a dependency injection style library that let us define the design of our application (in code) using an internal domain specific language (DSL).

The library let us define Systems and to compose those systems from other systems. A system represented a set of objects that implemented interfaces within a scope. The system/subsystem could choose to expose interfaces to the parent scope.

The effect of this was that mixins came for free. You would just add the class implementing the slice of behaviour to your system definition and expose its interface to the parent scope. That system now has that behaviour.

You maybe be able to do this with modern dependency injection frameworks too.

We were using NDI (https://github.com/NigelThorne/ndependencyinjection/wiki).

Note: I wrote NDI in back in 2008.

查看更多
该账号已被封号
5楼-- · 2019-01-21 11:37

You can create a DynamicObject that forwards the calls it receives to a list of targets, in a chain of responsibility style (note that polymorphic dispatch also works like this - from the most derived class upwards):

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

}

Note that you'd also want to implement delegation for properties (TryGetMember and TrySetMember), indexers (TryGetIndex and TrySetIndex) and operators (TryBinaryOperation and TryUnaryOperation).

Then, given a set of classes:

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

class MyOtherClass {
  public void MyOtherClassMethod() {
    Console.WriteLine("MyOtherClass::Method");
  }
}

You can "blend" them all together:

dynamic blend = new Composition(new MyClass(), new MyOtherClass());
blend.MyClassMethod();
blend.MyOtherClassMethod();

You can also extend the dynamic object to use classes' attributes or other kinds of annotations to look for mixins. For example, given this annotation interface:

public interface Uses<M> where M : new() { }

You can have this 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);
  }

}

And create your "blends" like this:

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();
查看更多
beautiful°
6楼-- · 2019-01-21 11:47

I've been working on an open source Mixin framework for C# pMixins. It leverages partial classes and code generators to wire in the Mixin class into the 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();
   }
}
查看更多
登录 后发表回答