C#中的匿名内部类

23

我正在编写一个C# Wicket实现,以加深对C#和Wicket的理解。我们遇到的问题之一是Wicket大量使用匿名内部类,而C#没有匿名内部类。

因此,例如在Wicket中,您可以像这样定义一个Link:

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#中模拟这样的东西?


1
我在你给出的例子中没有看到匿名内部类。如果你想让抽象类的实现者始终实现某些方法,你可以在类中创建一个抽象方法或让它实现一个接口。 - tenor
2
@tenor,这里定义了一个内联匿名类,继承自 Link 并重写 onClick 方法。与 Java 不同,C# 不支持匿名类从给定的用户类型派生。 - Darin Dimitrov
@Darin Dimitrov,感谢指出这一点。我正在寻找一个真正的“内部/嵌套”类。提供的示例看起来更像是从现有类派生的匿名类,至少在C#术语中是这样。 - tenor
老话题了,但是它仍然存在...那是一个内部类,因为在Java中每个类都是顶级类或嵌套类(定义在另一个类内),并且可以是静态或非静态类。那是一个非静态嵌套类,术语称之为内部类。 - nasch
3个回答

17

你可以将代理设置为Link类的构造函数的一部分。这样用户就必须添加它。

public class Link 
{
    public Link(string id, Action handleStuff) 
    { 
        ...
    }

}

那么你可以这样创建一个实例:

var link = new Link("id", () => do stuff);

4
这是我会做的事情:
保留Link作为一个抽象类,使用工厂来实例化它,并将你的闭包/匿名方法作为参数传递给工厂的build方法。这样,你就可以保持原始设计中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");

美妙之处在于它使用了一个初始化块,允许您在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的构造函数中,从而避免使用工厂。

这个答案是三年前的,但我想提一下,在你的最终答案中,你可以把返回行缩短一点: return new Link(ID, OnClick ?? DefaultAction, OnFoo ?? DefaultAction, OnBar ?? DefaultAction); - Justin Edwards

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;

}

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