Как я могу привязать обработчик к статическому событию, не вызывая статический конструктор? - PullRequest
0 голосов
/ 29 июня 2018

Боюсь, я знаю ответ на этот вопрос, но ...

Можно ли успешно привязать статическое событие, которое будет вызываться во время статического конструктора? Или это логически невозможно?

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

MyClass.MyEvent += MyEventHandler;

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

Это правильно? Есть ли другой способ сделать это? Можно ли вообще связать статическое событие, не вызывая статический конструктор для выполнения?

Ответы [ 3 ]

0 голосов
/ 29 июня 2018

Когда я впервые прочитал этот вопрос, я сразу подумал: «Ни за что ...». Но тогда я был как "подожди секунду .. это может быть возможно .. хммм". Хорошо, давайте перейдем к коду! Учтите следующее:

abstract class FooBaseNotifier
{
    public static event Action FooTypeLoaded;

    protected static void Notify() => FooTypeLoaded?.Invoke();
}

class Foo:FooBaseNotifier
{
    static Foo() => Notify();
}

Ключевым моментом здесь является игра со статическими конструкторами C # правила . Мы используем абстрактный базовый класс для определения события и защищенный метод для его вызова. Он должен быть защищен, чтобы Foo мог запустить событие из статического конструктора.

Сейчас тестирую:

FooBaseNotifier.FooTypeLoaded += () => Console.WriteLine("Foo");
var foo = new Foo();

Console.ReadLine();

Это работает! Это выведет «Foo» на консоль. Кроме того, из-за того, что статическое событие является статическим членом абстрактного базового класса , вы можете сделать даже это:

Foo.FooTypeLoaded += () => Console.WriteLine("Foo");
var foo = new Foo();

Console.ReadLine();

И статический конструктор Foo не вызывается, но пока не будет достигнута строка с new Foo()!

0 голосов
/ 29 июня 2018

Краткий ответ: нет.

Более длинный ответ: Поместите обработчик в другой класс. FooHandlers.MyEvent. Затем просто статический конструктор Foo вызовет эти события. Вам нужно создать несколько методов FooHandlers.InvokeMyEvent, поскольку Foo не сможет сделать это напрямую.

Что касается «почему», статические конструкторы запускаются раньше, и к полю типа обращаются. Событие имеет вспомогательное поле для хранения многоадресного делегата. Это означает, что доступ к событию неизбежно вызывает статический конструктор, прежде чем его можно будет назначить (в конце концов, это своего рода точка статического конструктора).

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

0 голосов
/ 29 июня 2018

Когда вы объявляете событие, используя ключевое слово event, вы на самом деле объявляете два метода, add и remove. Думайте об этом, как когда вы объявляете свойство: под прикрытием вы действительно объявляете метод set и get. События ничем не отличаются; вы можете переопределить add и remove в коде, с помощью специального обработчика событий .

Так что, когда вы звоните

MyClass.MyEvent += MyEventHandler;

Вы действительно звоните

MyClass.MyEvent.add(MyEventHandler);  //not real code

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

Если вы хотите, чтобы некоторая инициализация выполнялась позже, извлеките ее в отдельный метод и вызовите ее отдельно или загрузите ее лениво, как в моем примере. Другой подход заключается в том, чтобы назначать обработчика лениво, как предлагает Такион в своем ответе. Я предпочитаю свой ответ, потому что это общий шаблон, и я бы хотел, чтобы другие инженеры понимали, что происходит в моем коде, особенно если для вашего приложения важно, чтобы логика инициализации выполнялась в определенное время.

static public class MyClass
{
    static bool _initialized = false;

    static MyClass()
    {
        Console.WriteLine("Test.ctor called");
    }

    static void Initialize()
    {
        Console.WriteLine("Test.Initialize called");
        _initialized = true;
    }

    static public event EventHandler MyEvent;

    static public void RaiseMyEvent()
    {
        if (!_initialized) Initialize();
        if (MyEvent != null) MyEvent(typeof(MyClass), new EventArgs());
    }
}
...