Каков наилучший способ динамического внедрения интерфейса в С#?

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

В Java есть функция под названием Анонимные классы, которая позволяет реализовать интерфейс "inline". Таким образом, мой вопрос: каким образом вы можете придумать что-то подобное в С#, используя существующий синтаксис (и я понимаю, что "самый приятный" субъективен). Я ищу хороший синтаксис, не обязательно производительность.

Я реализовал следующее как POC в С#:

Учитывая

interface IFoobar
{
   Boolean Foobar(String s);
}

IFoobar foo = Implement.Interface<IFoobar>(new {
   Foobar = new Func<String, Boolean>(s => s == "foobar")
});

В этом случае используется анонимный объект и некоторое отражение/излучение для реализации интерфейса IFoobar (с учетом свойств, общих методов и перегрузки). Но я не поклонник материала new Func<...>, но не могу обойтись без него.

Оглядываясь назад, я заметил библиотеку под названием Интерфейс Impromptu, но не был впечатлен ее синтаксисом для поддержки методов.

Есть ли более приятный способ?

Изменить. Я не ищу войн пламени Java vs С#.

Ответы

Ответ 1

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

Например, используя Moq библиотеку в качестве примера:

public interface IFoobar {
   Boolean Foobar(String s);
}  

void Main() {
    var foo = new Mock<IFoobar>();
    foo.Setup(x => x.Foobar(It.IsAny<string>()))
       .Returns((string s) => s == "foobar");

    foo.Object.Foobar("notbar"); // false
    foo.Object.Foobar("foobar"); // true
}

Ответ 2

Взгляните на "impromptu-interface" (https://github.com/ekonbenefits/impromptu-interface).

Это позволит вам сделать что-то вроде...

class Program
{
    static void Main(string[] args)
    {
        Bar b = new Bar();
        b.DoSomethingWithFoo(new
        {
            Foobar = Return<string>.Arguments<string>(r => "foo")
        }.ActLike<IFoo>());
    }
}

public interface IFoo
{
    string Foobar(String s);
}

public class Bar
{
    public void DoSomethingWithFoo(IFoo foo)
    {
        Console.WriteLine(foo.Foobar("Hello World"));
    }
}

Ответ 3

public class Foo
{
   public Func<string,bool> TheDelegate {get;set;}
}

public class Bar
{
   public bool Implementation(string s)
   {
      return s == "True";
   }
}

public class Usage
{
    var myBar = new Bar();
    var myFoo = new Foo { TheDelegate = myBar.Implementation };

    //Or

    var myFoo = new Foo { TheDelegate = x => x == "True" };   
    //This removes the need for Bar completely
}

Как вы можете видеть в приведенном выше примере, java-подобные хаки полностью не нужны в С#, что намного лучше.

Ответ 4

Хороший способ сделать то, что вам нужно на С#, можно использовать Объекты глины:

public interface IFoobar{
  Func<string, bool> Foobar { get; set; }  
}

С помощью этого интерфейса вы можете сделать что-то вроде этого:

dynamic New = new ClayFactory();

var foobar= New.FooBar();
foobar.Foobar = new Func<string, bool>(s => s == "foobar");

// Concrete interface implementation gets magically created!
IFoobar lou = foobar;
var result =lou.Foobar("foo");// return false

Что делает магию возможной, так это то, что Clay переопределяет оператор литья и создает динамический прокси для интерфейса (используя Castle), который делегирует члены объекту Clay.

Другим способом может быть использование библиотеки Impromptu Interface, которая позволяет обернуть любой объект с помощью интерфейса. Это означает, что любые объекты теперь могут иметь динамическое поведение. Если у объекта есть методы интерфейса, вы можете напрямую приложить к ним поведение по мере необходимости. Если у объекта нет методов интерфейса, вы определяете интерфейс и обертываете в нем объект, затем при необходимости добавляете поведения к методам интерфейса. Эта библиотека является автоматическим способом применения Шаблон адаптера объекта.

Если у вас есть такой интерфейс:

public Interface IFoobar
{
   bool Foobar(string s);
}

Вы можете украсить анонимный тип, как показано ниже:

//Anonymous Class
var anon = new {Foobar= Return<bool>.Arguments<string>(s => s == "foobar")};

var myInterface = anon.ActLike<IFoobar>();

Или вы можете использовать ExpandoObject:

dynamic expando = Build<ExpandoObject>.NewObject(Foobar: Return<bool>.Arguments<string>(s => s == "foobar"));

IMyInterface myInterface = Impromptu.ActLike(expando);

Если вы хотите реализовать более одного интерфейса, проверьте мой ответ в этой сообщении.

Ответ 5

Можно сделать чистый синтаксис лямбда, но за счет проверки статического типа внутри Create().

Я смог использовать ImpromptuInterface для этого:

IFoobar foo = Implement.Interface(new {
    Foobar = Function.Create(s => s == "foobar"),
});

Создав следующие классы:

public static class Implement{

    public static dynamic Interface(object source){
        return Impromptu.ActLike(source);
    }

}

public static class Function{

    public static Func<dynamic> Create(Func<dynamic> del){
        return del;
    }

    public static Func<dynamic,dynamic> Create(Func<dynamic,dynamic> del){
        return del;
    }
    public static Func<dynamic,dynamic,dynamic> Create(Func<dynamic,dynamic, dynamic> del){
        return del;
    }

    public static Func<dynamic,dynamic,dynamic,dynamic> Create(Func<dynamic,dynamic, dynamic,dynamic> del){
        return del;
    }
    //...Add more if you want
}

Ответ 6

К сожалению, анонимные классы в С# не могут реализовывать интерфейсы, как в Java. Однако вы можете создать какой-то адаптер-класс без каких-либо дополнительных зависимостей от внешних проектов. Просто создайте базовый класс, который реализует ваш интерфейс с помощью Func:

interface IFoo
{
    bool DoSomething(string value);
}
class Bar : IFoo
{
    private readonly Func<string, bool> m_DoSomething;
    public Bar(Func<string, bool> DoSomething) { this.m_DoSomething = DoSomething; }

    public bool DoSomething(string value)
    { 
        return this.m_DoSomething(value);
    } 
}

Теперь вы можете вызвать его так:

var result = new Bar(x => true);

Или также используя named arguments, который является более очевидным, особенно если ваш интерфейс имеет более одного метода:

var result = new Bar(DoSomething: x => true);

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

Ответ 7

Если ваша самая большая жалоба реализует интерфейс где-то в другом месте, почему бы не создать вложенный класс непосредственно перед/после вашего метода? (Сравните со статическим вложенным классом Java.)

Это более идиоматический С#, чем создание/использование некоторого динамического фрейма (в основном статический вложенный класс.)