Подтвердить что ты не робот

Анонимные внутренние классы в С#

Я в процессе написания реализации С# Wicket, чтобы углубить свое понимание С# и Wicket. Одна из проблем, с которыми мы сталкиваемся, - это то, что Wicket сильно использует анонимные внутренние классы, а С# не имеет анонимных внутренних классов.

Итак, например, в Wicket вы определяете ссылку следующим образом:

Link link = new Link("id") {
    @Override
    void onClick() {
        setResponsePage(...);
    }
};

Поскольку Link является абстрактным классом, он заставляет разработчика реализовать метод onClick.

Однако в С#, поскольку нет анонимных внутренних классов, нет никакого способа сделать это. В качестве альтернативы вы можете использовать такие события:

var link = new Link("id");
link.Click += (sender, eventArgs) => setResponsePage(...);

Конечно, есть пара недостатков с этим. Прежде всего, может быть несколько обработчиков Click, что может и не быть круто. Он также не заставляет конструктора добавлять обработчик Click.

Другим вариантом может быть просто свойство закрытия, подобное этому:

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

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

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

4b9b3361

Ответ 1

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

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

}

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

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

Ответ 2

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

Сохраните ссылку как абстрактный класс, используйте Factory, чтобы создать экземпляр и передать свой метод закрытия/аноним в качестве параметра для метода сборки Factory. Таким образом, вы можете сохранить свой оригинальный дизайн с помощью Link как абстрактного класса, вытесняя реализацию через factory и все еще скрывая какой-либо конкретный след ссылки внутри factory.

Вот пример кода:

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

EDIT: На самом деле, это может быть немного ближе к тому, что вы хотите:

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

Это близко к тому, что вы ищете, но я думаю, что мы можем сделать еще один шаг вперед с необязательными именованными аргументами, функцией С# 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 существует метод Factory Build, который принимает 1 обязательный параметр (ID) и 3 необязательных параметра, OnClick, OnFoo и OnBar. Если они не назначены, метод Factory предоставляет им реализацию по умолчанию.

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

Недостаток, однако, находится в последнем примере, класс Link не является абстрактным. Но он не может быть создан за пределами класса Link, потому что его конструктор является частным (принудительное использование класса Builder для создания ссылки).

Вы также можете напрямую перенести необязательные параметры в конструктор ссылок, избегая при этом необходимости Factory.

Ответ 3

Я начал это перед хорошим ответом @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;

}