Каков наилучший способ динамического внедрения интерфейса в С#?
Я часто нахожу довольно отвлекающим фактором, чтобы реализовать интерфейс только потому, что он мне нужен один раз для вызова метода. Я должен создать класс где-то еще, реализовать интерфейс и т.д. И т.д.
В 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.)
Это более идиоматический С#, чем создание/использование некоторого динамического фрейма (в основном статический вложенный класс.)