在C#中,最好的动态实现接口的方式是什么?

19

我经常觉得需要实现一个接口很分散注意力,因为我只需要它来调用某个方法一次。我必须在其他地方创建一个类,实现接口等等。

Java有一个名为匿名类的功能,允许我们在“内联”实现接口。我的问题是:你能想到使用现有语法在C#中实现类似功能的最好方式是什么(我意识到"最好"是主观的)?我寻找漂亮的语法,不一定是性能。

我在C#中编写了以下内容作为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 Interface的库,但对其支持方法的语法并不印象深刻。

有没有更好的方法呢?

编辑:我不想看到Java与C#之间的争吵。


3
有时我觉得必须要实现一个接口仅仅是因为要调用其中的一个方法,这让我很分心。使用委托可以解决这个问题,这也是C#和Java之间的区别所在。 - Federico Berasategui
你经常发现自己这样做吗? - John Saunders
不,偶尔会这样,也许在编写单元测试时更常见。我意识到这并不一定是好的实践,但我主要是出于好奇。 - Marcus
我的问题不是关于Java的匿名类与C#委托的比较。我的回答也不是。但如果你不想听,那对我来说完全没问题。 - Sergey Kalinichenko
1
嗯,ImpromptuInterface 有你精确的 POC 语法,只需将 Implement.Interface 替换为 Impromptu.ActLike,这样 IFoobar foo = Impromptu.ActLike<IFoobar>(new { Foobar = new Func<String, Boolean>(s => s == "foobar") }); 就可以了。 - jbtule
Imprmoptu的方法 Return<bool>.Arguments<string> 太好了,可以使用 public static class Return<TR> { public static Func<T1,TR> Arguments<T1>(Func<T1,TR> del) { return del; }} 来强制转换为一个 Func<string, bool> - jbtule
7个回答

7

您提到您不需要经常这样做,不关心性能,并且通常想在单元测试期间执行。为什么不使用模拟框架呢?

例如,以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
}

6

请看 "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"));
    }
}

5

在C#中实现你需要的功能的一个好方法是使用Clay对象:

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 库,它允许您使用接口包装任何对象。这意味着任何对象都可以具有动态行为。如果对象具有接口方法,则可以根据需要直接附加行为。如果对象没有接口方法,则定义一个接口并在其中包装对象,然后按需附加行为到接口方法。此库是应用 Object Adapter pattern 的自动方式。
如果您有一个如下所示的接口:
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);

如果您想实现多个接口,请查看我在post中的答案。


1
很不幸,在C#中,匿名类不能像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);

或者也可以使用命名参数,特别是当您的接口有多个方法时,这更加明显:

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

唯一的缺点是你需要为每个接口都编写一个实现类。因此,如果您想多次实现相同的接口以获得不同的行为,这种方法才有用。所以,每当我需要相同接口的不同实现时,我就使用这种方法。

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

正如您在上面的例子中看到的,C#语言完全不需要类似于Java的hack,它是一种更好的语言。

2
这个例子没有实现原问题中的 IFoobar 接口。你如何将实现传递给一个接受 IFoobar 实例的方法? - user10789
@user10789,在C#中你不需要这样的接口,因为有委托。从你的代码中完全删除该接口,并在需要定义这种“可互换方法”时放置一个委托声明即可。 - Federico Berasategui
3
@Marcus说:“只是因为我需要某个方法调用而实现一个接口”...“特别是在编写单元测试时”。我猜他想将一个实现传递给一个方法,然后该方法会调用一个或多个接口的方法。对于这种情况或任何您无法更改所调用代码的情况下,委托都无法使用。 - user10789

1
可以使用更简洁的Lambda语法,但代价是在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
}

1
如果您最大的抱怨是在其他地方实现接口,为什么不在方法之前/之后直接创建一个嵌套类?(与Java静态嵌套类进行比较。)这比创建/使用某些动态框架更符合C#的习惯用法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接