Каков наилучший способ работы с необязательными делегатами в конструкторе С#?

Я рефакторинг функции, которая принимает необязательный делегат через конструктор. Делегат запускается, когда в классе инициируются события. Если делегат не передан, вместо него используется локальная функция по умолчанию:

public class Foo
{
    int    _memberVariable;
    readonly Action _onEventOne;
    readonly Action _onEventTwo;

    public Foo(Action onEventOne, Action onEventTwo = null)
    {
        _memberVariable = 0;

        _onEventOne = onEventOne;
        _onEventTwo = onEventTwo ?? DefaultEventTwo;

        _onEventOne();
    }

    private void DefaultEventTwo()
    {
        ++_memberVariable;
    }
}

Я хочу удалить значение по умолчанию (это общедоступный интерфейс, поэтому перегрузка будет предпочтительнее), и это в производственном коде, поэтому я не хочу менять интерфейс, если мне это не нужно.

В идеальном мире я бы использовал цепочку конструкторов:

public Foo(Action onEventOne) : this(onEventOne, DefaultEventTwo)
{
    //CS0120 An object reference is required for the non-static field, method, or property 'Foo.DefaultEventTwo()
}

(Я понимаю, почему это не работает, просто привожу пример решения, которое я бы использовал, если бы это не был конструктор).

Поскольку делегаты доступны только для чтения, я не могу установить их в общей функции инициализации.

Есть ли лучший способ справиться с таким случаем, чем просто передать значение null, а затем перехватить его в главном конструкторе? Это не кажется очень элегантным, и я хотел бы в идеале иметь возможность поймать нулевое действие как исключение (например, если внешний вызывающий объект использовал null вместо использования перегруженного конструктора). Я мог бы удалить только чтение из делегатов, но опять же это не похоже на отличное решение, поскольку они действительно доступны только для чтения.

Любые мысли будут оценены.


person Linden Ryuujin    schedule 09.12.2016    source источник
comment
общедоступный Foo(Action onEventOne) : this(onEventOne, null) был бы способом или public Foo(Action onEventOne) : this(onEventOne, new Action(Console.Beep)), или у вас есть функция вроде: public static void NoEvent() { // DoNothing() } и обрабатывайте ее так: ...,new Action(NoEvent)   -  person TripleEEE    schedule 09.12.2016
comment
И затем вы просто проверяете null и, если он проходит, вызываете DefaultEventTwo вместо (не) предоставленного делегата.   -  person HimBromBeere    schedule 09.12.2016


Ответы (2)


Единственный способ, которым я мог заставить это работать, состоял в том, чтобы заставить его работать уродливо (по моему собственному мнению).

Вы должны предоставить статический метод, но этот статический метод может использовать ссылку на это, чтобы получить фактический метод.

Это то, что я придумал.

public Foo(Action onEventOne) : this(onEventOne, self => self.DefaultEventTwo)
{
    //CS0120 An object reference is required for the non-static field, method, or property 'Foo.DefaultEventTwo()
}

public Foo(Action onEventOne, Action onEventTwo = null) : this(onEventOne, self => onEventTwo)
{ }

// private constructor, just for the sake of getting it working
private Foo(Action onEventOne, Func<Foo, Action> onEventTwo = null)
{
    _memberVariable = 0;

    _onEventOne = onEventOne;
    _onEventTwo = onEventTwo(this); // <--

    _onEventOne();
}

self => self.DefaultEventTwo — это статическая функция для получения действия. Эта функция используется при вызове onEventTwo(this) для получения события по умолчанию экземпляра this.

person Patrick Hofman    schedule 09.12.2016
comment
Я подозреваю, что не будет никакого действительно хорошего решения для этого, мне в основном интересно посмотреть, какие идеи были у людей, и это, безусловно, самое интересное на данный момент. Это определенно хорошо работает для проблемы, как указано, но начинает немного сбивать с толку, если вы добавите дополнительные необязательные события (очевидно, это не отметка против этого, но просто стоит отметить, если кто-то еще столкнется с этим в будущем). - person Linden Ryuujin; 12.12.2016
comment
Да, просто невозможно делать то, что вы хотите, не делая его статичным или перемещая его в конструктор, как в другом ответе. - person Patrick Hofman; 12.12.2016

Я что-то пропустил?

public class Foo
{
    int    _memberVariable;
    readonly Action _onEventOne;
    readonly Action _onEventTwo;

    public Foo(Action onEventOne): this(onEventOne, null) { }
    public Foo(Action onEventOne, Action onEventTwo)
    {
        _memberVariable = 0;

        _onEventOne = onEventOne;
        _onEventTwo = onEventTwo ?? DefaultEventTwo;

        _onEventOne();
    }

    private void DefaultEventTwo()
    {
        ++_memberVariable;
    }
}

Все, что вам нужно сделать, это удалить значение по умолчанию и создать новый конструктор, который имеет только один аргумент. Теперь в самом подробном конструкторе (исходном) проверьте, является ли предоставленное значение null, и если да, установите _onEventTwo в DefaultEventTwo.

Чтобы никто не использовал сокращенный конструктор, просто сделайте его internal.

РЕДАКТИРОВАТЬ: Относительно обработки исключений. Как насчет использования внутреннего конструктора в качестве «основного», который все остальные вызывают с параметром, указывающим, откуда пришел вызов:

internal Foo(Action onEventOne): this(onEventOne, null, true) { }
// public API: NULL not allwoed as param
public Foo(Action onEventOne, Action onEventTwo) : this(onEventOne, onEventTwo, false) { }
internal Foo(Action onEventOne, Action onEventTwo, bool internalUse)
{
    _memberVariable = 0;

    _onEventOne = onEventOne;
    if(onEventTwo == null)
    {
        if(!internalUse) throw new ArgumentNullException("onEventTwo");
        else this._onEventTwo = DefaultEventTwo;
    }
    _onEventOne();
}
person HimBromBeere    schedule 09.12.2016
comment
Я думаю, что ваше второе решение намного лучше подходит к вопросу из двух (возможно, я сформулировал вопрос сбивчиво - ваше первое решение было тем, что я сделал первым, но я ищу альтернативы). Добавление дополнительного аргумента — интересная идея, которую я даже не рассматривал (теперь это кажется таким очевидным), спасибо! - person Linden Ryuujin; 12.12.2016