Анонимные внутренние классы в C # - PullRequest
22 голосов
/ 22 января 2011

Я нахожусь в процессе написания реализации 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.

Другой вариант может состоять в том, чтобы просто иметь свойство closure, например:

var link = new Link("id");
link.Click = () => setResponsePage(...);

Это решает проблему наличия множества обработчиков,но все же не заставляет разработчика добавлять обработчик.

Итак, мой вопрос: как вы эмулируете что-то подобное в идиоматическом C #?

Ответы [ 3 ]

17 голосов
/ 22 января 2011

Вы можете сделать делегат частью конструктора класса Link. Таким образом, пользователь должен будет добавить его.

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

}

Затем вы создаете экземпляр следующим образом:

var link = new Link("id", () => do stuff);
3 голосов
/ 25 декабря 2012

Вот что я бы сделал:

Сохраните 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");

Прелесть в том, что он использует блок инициализации, позволяя вам объявить как можно большенеобязательные реализации действий в классе 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,избегая необходимости фабрики в целом.

1 голос
/ 22 января 2011

Я начал это до того, как @ 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;

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...