我正在编写C#Wicket实现,以加深我对C#和Wicket的理解。我们遇到的一个问题是Wicket大量使用匿名内部类,而C#没有匿名内部类。
因此,例如,在Wicket中,您可以像这样定义一个链接:
Link link = new Link("id") {
@Override
void onClick() {
setResponsePage(...);
}
};
由于Link是一个抽象类,它强制实现者实现onClick方法。
但是,在C#中,由于没有匿名内部类,因此无法执行此操作。作为替代方案,您可以使用以下事件:
var link = new Link("id");
link.Click += (sender, eventArgs) => setResponsePage(...);
当然,这有一些缺点。首先,可以有多个Click处理程序,这可能不太酷。它也不会强制实现者添加Click处理程序。
另一种选择可能是只有一个这样的闭包属性:
var link = new Link("id");
link.Click = () => setResponsePage(...);
这解决了拥有多个处理程序的问题,但仍然没有强制实现者添加处理程序。
所以,我的问题是,你如何在惯用的C#中模仿这样的东西?
答案 0 :(得分:17)
您可以使委托成为Link类的构造函数的一部分。这样用户就必须添加它。
public class Link
{
public Link(string id, Action handleStuff)
{
...
}
}
然后以这种方式创建实例:
var link = new Link("id", () => do stuff);
答案 1 :(得分:3)
这就是我要做的事情:
将Link保留为抽象类,使用Factory实例化它并将闭包/匿名方法作为Factory的构建方法的参数传递。这样,您可以将Link的原始设计保留为抽象类,强制通过工厂实现,并且仍然隐藏工厂内任何具体的Link痕迹。
以下是一些示例代码:
class Program
{
static void Main(string[] args)
{
Link link = LinkFactory.GetLink("id", () =>
// This would be your onClick method.
{
// SetResponsePage(...);
Console.WriteLine("Clicked");
Console.ReadLine();
});
link.FireOnClick();
}
public static class LinkFactory
{
private class DerivedLink : Link
{
internal DerivedLink(String id, Action action)
{
this.ID = id;
this.OnClick = action;
}
}
public static Link GetLink(String id, Action onClick)
{
return new DerivedLink(id, onClick);
}
}
public abstract class Link
{
public void FireOnClick()
{
OnClick();
}
public String ID
{
get;
set;
}
public Action OnClick
{
get;
set;
}
}
}
编辑:实际上,这可能会更接近您想要的内容:
Link link = new Link.Builder
{
OnClick = () =>
{
// SetResponsePage(...);
},
OnFoo = () =>
{
// Foo!
}
}.Build("id");
美妙之处在于它使用了一个init块,允许您根据需要在Link类中声明多个可选的动作实现。
这是相关的Link类(使用密封的Builder内部类)。
public class Link
{
public sealed class Builder
{
public Action OnClick;
public Action OnFoo;
public Link Build(String ID)
{
Link link = new Link(ID);
link.OnClick = this.OnClick;
link.OnFoo = this.OnFoo;
return link;
}
}
public Action OnClick;
public Action OnFoo;
public String ID
{
get;
set;
}
private Link(String ID)
{
this.ID = ID;
}
}
这与您正在寻找的内容非常接近,但我认为我们可以通过可选的命名参数(C#4.0功能)更进一步。让我们看看带有可选命名参数的Link的示例声明:
Link link = Link.Builder.Build("id",
OnClick: () =>
{
// SetResponsePage(...);
Console.WriteLine("Click!");
},
OnFoo: () =>
{
Console.WriteLine("Foo!");
Console.ReadLine();
}
);
为什么这很酷?让我们看一下新的Link类:
public class Link
{
public static class Builder
{
private static Action DefaultAction = () => Console.WriteLine("Action not set.");
public static Link Build(String ID, Action OnClick = null, Action OnFoo = null, Action OnBar = null)
{
return new Link(ID, OnClick == null ? DefaultAction : OnClick, OnFoo == null ? DefaultAction : OnFoo, OnBar == null ? DefaultAction : OnBar);
}
}
public Action OnClick;
public Action OnFoo;
public Action OnBar;
public String ID
{
get;
set;
}
private Link(String ID, Action Click, Action Foo, Action Bar)
{
this.ID = ID;
this.OnClick = Click;
this.OnFoo = Foo;
this.OnBar = Bar;
}
}
在静态类Builder中,有一个工厂方法Build,它接受1个必需参数(ID)和3个可选参数,OnClick,OnFoo和OnBar。如果未分配它们,则工厂方法为它们提供默认实现。
因此,在构造函数的Link参数参数中,您只需要实现所需的方法,否则它们将使用默认操作,这可能就是什么。
然而,缺点是在最后一个例子中,Link类不是抽象的。但是它不能在Link类的范围之外实例化,因为它的构造函数是私有的(强制使用Builder类来实例化Link)。
你也可以直接将可选参数移动到Link的构造函数中,完全不需要工厂。
答案 2 :(得分:1)
我在@ meatthew的好答案之前开始这个 - 除了我会从一个抽象的基类开始之外我几乎完全相同 - 所以如果你不想走匿名实现的路线你将是也可以自由地做到这一点。
public abstract class LinkBase
{
public abstract string Name { get; }
protected abstract void OnClick(object sender, EventArgs eventArgs);
//...
}
public class Link : LinkBase
{
public Link(string name, Action<object, EventArgs> onClick)
{
_name = Name;
_onClick = onClick;
}
public override string Name
{
get { return _name; }
}
protected override void OnClick(object sender, EventArgs eventArgs)
{
if (_onClick != null)
{
_onClick(sender, eventArgs);
}
}
private readonly string _name;
private readonly Action<object, EventArgs> _onClick;
}