Рассматривать события как объекты - PullRequest
0 голосов
/ 30 августа 2010

C # все еще недостаточно ОО?Здесь я привожу (может быть, плохой) пример.

public class Program
{
   public event EventHandler OnStart;
   public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts");

   public class MyCSharpProgram
   {
      public string Name { get; set; }
      public event EventHandler OnStart;
      public void Start()
      {
          OnStart(this, EventArgs.Empty);
      }
   }

   static void Main(string[] args)
   {
      MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" };
      cs.OnStart += LogOnStart;  //can compile
      //RegisterLogger(cs.OnStart);   // Line of trouble
      cs.Start();   // it prints "start" (of course it will :D)

      Program p = new Program();
      RegisterLogger(p.OnStart);   //can compile
      p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime
      Console.Read();
    }

    static void RegisterLogger(EventHandler ev)
    {
      ev += LogOnStart;
    }
}

RegisterLogger (cs.OnStart) приводит к ошибке компиляции, поскольку «Событие XXX может появляться только в левой части + = или - = blabla».Но почему RegisterLogger (p.OnStart) может?Между тем, хотя я зарегистрировал p.OnStart, он также вызовет исключение NullReferenceException, и, похоже, p.OnStart не «действительно» передается методу.

Ответы [ 4 ]

1 голос
/ 30 августа 2010

"Событие XXX может появляться только в левой части + = или - = blabla"

Это на самом деле потому, что C # - это"OO достаточно". Одним из основных принципов ООП является инкапсуляция; события предоставляют форму этого, как и свойства: внутри объявленного класса они могут быть доступны напрямую, но снаружи они доступны только для операторов += и -=. Это сделано для того, чтобы декларирующий класс полностью контролировал время вызова событий. Код клиента может иметь только слово в , что происходит , когда они вызываются.

Причина, по которой ваш код RegisterLogger(p.OnStart) компилируется, состоит в том, что он объявлен из области действия Program класса, где объявлено событие Program.OnStart.

Причина, по которой ваш код RegisterLogger(cs.OnStart) не не компилируется , заключается в том, что он объявлен изнутри класса Program, но событие MyCSharpProgram.OnStart объявлено (очевидно) в MyCSharpProgram класс.

Как указывает Крис Тейлор , причина, по которой вы получаете NullReferenceException в строке p.OnStart(p, EventArgs.Empty);, заключается в том, что при вызове RegisterLogger при его наличии назначает новое значение локальной переменной не оказывает влияния на объект, которому была назначена эта локальная переменная, когда она была передана в качестве параметра . Чтобы лучше это понять, рассмотрим следующий код:

static void IncrementValue(int value)
{
    value += 1;
}

int i = 0;
IncrementValue(i);

// Prints '0', because IncrementValue had no effect on i --
// a new value was assigned to the COPY of i that was passed in
Console.WriteLine(i);

Так же, как метод, который принимает int в качестве параметра и присваивает ему новое значение, влияет только на локальную переменную, скопированную в его стек, метод, который принимает EventHandler в качестве параметра и назначает новое значение it влияет только на ее локальную переменную (в присваивании).

1 голос
/ 30 августа 2010

Причина, по которой это не скомпилировано:

RegisterLogger (cs.OnStart);

... означает, что обработчик события и метод, которому вы его передаете, находятся в разных классах. C # обрабатывает события очень строго и позволяет только классу, в котором появляется событие, делать что-либо кроме добавления обработчика (включая передачу его функциям или вызов его).

Например, это тоже не скомпилируется (потому что он в другом классе):

cs.OnStart(cs, EventArgs.Empty);

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

static void RegisterLogger(ref EventHandler ev)
{
    ev += LogOnStart;
}
1 голос
/ 30 августа 2010

Внесите следующее изменение в RegisterLogger, объявив ev в качестве ссылочного аргумента для обработчика событий.

static void RegisterLogger(ref EventHandler ev) 
{ 
  ev += LogOnStart; 
}

Тогда вашей точке вызова также нужно будет использовать ключевое слово 'ref' при вызове метода следующим образом

RegisterLogger(ref p.OnStart);
0 голосов
/ 30 августа 2010

Когда объект объявляет событие, он предоставляет только методы для добавления и / или удаления обработчиков к событию вне класса (при условии, что он не переопределяет операции добавления / удаления).Внутри него он обрабатывается во многом как «объект» и работает более или менее как объявленная переменная-делегат.Если к событию не добавлены обработчики, это как если бы оно никогда не было инициализировано и равно null.Именно так задумано.Вот типичный шаблон, используемый в фреймворке:

public class MyCSharpProgram
{
    // ...

    // define the event
    public event EventHandler SomeEvent;

    // add a mechanism to "raise" the event
    protected virtual void OnSomeEvent()
    {
        // SomeEvent is a "variable" to a EventHandler
        if (SomeEvent != null)
            SomeEvent(this, EventArgs.Empty);
    }
}

// etc...

Теперь, если вы должны настаивать на представлении делегата за пределами вашего класса, просто не определяйте его как событие.Затем вы можете рассматривать его как любое другое поле или свойство.

Я изменил ваш пример кода, чтобы проиллюстрировать:

public class Program
{
    public EventHandler OnStart;
    public static EventHandler LogOnStart = (s, e) => Console.WriteLine("starts");

    public class MyCSharpProgram
    {
        public string Name { get; set; }

        // just a field to an EventHandler
        public EventHandler OnStart = (s, e) => { /* do nothing */ }; // needs to be initialized to use "+=", "-=" or suppress null-checks
        public void Start()
        {
            // always check if non-null
            if (OnStart != null)
                OnStart(this, EventArgs.Empty);
        }
    }

    static void Main(string[] args)
    {
        MyCSharpProgram cs = new MyCSharpProgram { Name = "C# test" };
        cs.OnStart += LogOnStart;  //can compile
        RegisterLogger(cs.OnStart);   // should work now
        cs.Start();   // it prints "start" (of course it will :D)

        Program p = new Program();
        RegisterLogger(p.OnStart);   //can compile
        p.OnStart(p, EventArgs.Empty); //can compile, but NullReference at runtime
        Console.Read();
    }

    static void RegisterLogger(EventHandler ev)
    {
        // Program.OnStart not initialized so ev is null
        if (ev != null) //null-check just in case
            ev += LogOnStart;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...