Миксины с C # 4.0

Я встречал различные вопросы о том, можно ли создавать миксины на C #, и они часто направляются в проект повторного микширования на codeplex. Однако я не знаю, нравится ли мне концепция «полного интерфейса». В идеале я бы расширил класс так:

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

Просто добавив интерфейс Taggable, я могу создавать объекты типа MyClass через какую-то фабрику объектов. В возвращаемом экземпляре будут все члены, определенные в MyClass, а также все члены, предоставленные путем добавления атрибута тегирования (например, набора тегов). Похоже, это было бы легко сделать с помощью C # 4.0 (ключевое слово dynamic). В проекте повторного микширования используется C # 3.5. Есть ли у кого-нибудь хорошие способы расширения объектов через C # 4.0 без изменения самих классов? Спасибо.


person ActionJackson    schedule 11.07.2011    source источник
comment
Возможно частичные занятия? Способы расширения?   -  person George Johnston    schedule 11.07.2011
comment
Используя методы расширения, я бы написал более явный код, который сочетает MyClass с моим кодом, связанным с тегами (конечно, в дополнение к атрибуту taggable). Я бы хотел сделать это, не вступая в брак с ними.   -  person ActionJackson    schedule 11.07.2011


Ответы (5)


Вы можете создавать миксиноподобные конструкции в C # 4.0 без использования динамических, с методами расширения на интерфейсах и _ 1_ класс для хранения состояния. Идею можно найти здесь.

Вот пример:

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

Проблема использования атрибута заключается в том, что вы не можете передавать общие параметры из класса в миксин. Вы можете сделать это с помощью маркерных интерфейсов.

person Jordão    schedule 24.07.2011
comment
Черт возьми, это круто. Как у него так мало голосов? Язык без множественного наследования затрудняет написание миксинов. Это большой недостаток языков .NET и Java. Очень хороший пост в блоге! - person kevinarpe; 04.11.2012
comment
Подход хорош, но не позволяет полиморфно изменять методы из миксинов. Таким образом, ваш другой ответ намного мощнее (забавно, что вы можете добавить два ответа ...) - person Johannes Rudolph; 19.07.2015

Вы можете создать 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();
person Jordão    schedule 15.07.2012
comment
Хорошая идея. Как вы думаете, сериализаторы XML и JSON справятся с миксинами? - person Richard Schneider; 12.03.2015
comment
Некоторые люди, сталкиваясь с проблемой, думают, что я знаю, что я буду использовать динамическую типизацию. Теперь они динамически повторно отправляют Задачу и переводят ее в Решение и достигли нирваны программирования. - person tjbtech; 07.10.2015

Я работал над фреймворком Mixin с открытым исходным кодом для C # pMixins. Он использует частичные классы и генераторы кода для подключения класса 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();
   }
}
person Philip Pittle    schedule 09.05.2014
comment
+1 интересный фреймворк. Некоторое время назад я также начал нечто подобное. - person Jordão; 19.07.2014

Я знаю, что это старая тема, но я также хотел бы представить проект с открытым исходным кодом, над которым я сейчас работаю: mixinSharp.

Это расширение рефакторинга на основе Roslyn для Visual Studio 2015, которое добавляет поддержку миксинов в C # путем генерации необходимого кода делегирования.

Например, предположим, что у вас есть следующий код миксина, который вы хотите использовать повторно:

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

Если вы выполните шаг рефакторинга mixinSharp в поле NameMixin _name, расширение автоматически добавит весь связующий код, который требуется для включения миксина в ваш класс:

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.

person pgenfer    schedule 05.08.2016

В 2008 году я работал над проектом, используя библиотеку стилей внедрения зависимостей, которая позволяла нам определять дизайн нашего приложения (в коде) с использованием внутреннего языка, специфичного для предметной области (DSL).

Библиотека позволяет нам определять системы и составлять эти системы из других систем. Система представляет собой набор объектов, реализующих интерфейсы в пределах области видимости. Система / подсистема может выбрать предоставление интерфейсов родительской области.

В результате миксины приходили бесплатно. Вы просто добавляете класс, реализующий срез поведения, в определение вашей системы и предоставляете его интерфейс родительской области видимости. Теперь у этой системы такое поведение.

Возможно, вы также сможете сделать это с помощью современных фреймворков для внедрения зависимостей.

Мы использовали NDI (https://github.com/NigelThorne/ndependencyinjection/wiki).

Примечание: я написал NDI еще в 2008 году.

person Nigel Thorne    schedule 16.10.2018