Как вызвать событие при изменении значения переменной?

96

В настоящее время я создаю приложение на C # с помощью Visual Studio. Я хочу создать код, чтобы, когда переменная имеет значение 1, выполнялась определенная часть кода. Я знаю, что могу использовать оператор if, но проблема в том, что значение будет изменено в асинхронном процессе, поэтому технически оператор if можно игнорировать до того, как значение изменится.

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

Вполне возможно, что я мог неправильно понять, как работает оператор if! Любая помощь приветствуется.

Джеймс Манди
источник
1
Чтобы было ясно, наблюдение за изменением переменной возможно только для переменной, которой вы владеете (или которая уже связана с IObservable / INotifyPropertyChanged / Event). Вы не можете наблюдать изменение системной переменной, если оно не предназначено для наблюдения.
Cœur

Ответы:

124

Мне кажется, вы хотите создать недвижимость.

public int MyProperty
{
    get { return _myProperty; }
    set
    {
        _myProperty = value;
        if (_myProperty == 1)
        {
            // DO SOMETHING HERE
        }
    }
}

private int _myProperty;

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

Джонатан Вуд
источник
68

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

У вас может быть собственный делегат EventHandler или известный делегат System.EventHandler.

Обычно для этого есть шаблон:

  1. Определите общедоступное событие с помощью делегата обработчика событий (имеющего аргумент типа EventArgs).
  2. Определите защищенный виртуальный метод с именем OnXXXXX (например, OnMyPropertyValueChanged). В этом методе вы должны проверить, является ли делегат обработчика событий нулевым, и если нет, вы можете его вызвать (это означает, что к делегированию события прикреплены один или несколько методов).
  3. Вызывайте этот защищенный метод всякий раз, когда вы хотите уведомить подписчиков о каких-либо изменениях.

Вот пример

private int _age;

//#1
public event System.EventHandler AgeChanged;

//#2
protected virtual void OnAgeChanged()
{ 
     if (AgeChanged != null) AgeChanged(this,EventArgs.Empty); 
}

public int Age
{
    get
    {
         return _age;
    }

    set
    {
         //#3
         _age=value;
         OnAgeChanged();
    }
 }

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

Если вы хотите поймать событие в другом потоке, который вызывается, вы должны быть осторожны, чтобы не изменить состояние объектов, которые определены в другом потоке, что приведет к возникновению исключения между потоками. Чтобы избежать этого, вы можете использовать метод Invoke для объекта, состояние которого вы хотите изменить, чтобы убедиться, что изменение происходит в том же потоке, в котором возникло событие, или в случае, если вы имеете дело с формой Windows Form, которую вы можно использовать BackgourndWorker, чтобы легко и просто выполнять задачи в параллельном потоке.

Битлз1692
источник
3
Одно из лучших объяснений во всей сети. Думаю, я наконец понимаю обработку пользовательских событий. Благодарен за этот пост.
Прощай,
44

Платформа .NET фактически предоставляет интерфейс, который можно использовать для уведомления подписчиков об изменении свойства: System.ComponentModel.INotifyPropertyChanged. Этот интерфейс имеет одно событие PropertyChanged. Обычно он используется в WPF для привязки, но я нашел его полезным на бизнес-уровнях как способ стандартизации уведомлений об изменении свойств.

С точки зрения безопасности потоков я бы поставил блокировку в сеттере, чтобы вы не столкнулись с какими-либо условиями гонки.

Вот мои мысли в коде :):

public class MyClass : INotifyPropertyChanged
{
    private object _lock;

    public int MyProperty
    {
        get
        {
            return _myProperty;
        }
        set
        {
            lock(_lock)
            {
                //The property changed event will get fired whenever
                //the value changes. The subscriber will do work if the value is
                //1. This way you can keep your business logic outside of the setter
                if(value != _myProperty)
                {
                    _myProperty = value;
                    NotifyPropertyChanged("MyProperty");
                }
            }
        }
    }

    private NotifyPropertyChanged(string propertyName)
    {
        //Raise PropertyChanged event
    }
    public event PropertyChangedEventHandler PropertyChanged;
}


public class MySubscriber
{
    private MyClass _myClass;        

    void PropertyChangedInMyClass(object sender, PropertyChangedEventArgs e)
    {
        switch(e.PropertyName)
        {
            case "MyProperty":
                DoWorkOnMyProperty(_myClass.MyProperty);
                break;
        }
    }

    void DoWorkOnMyProperty(int newValue)
    {
        if(newValue == 1)
        {
             //DO WORK HERE
        }
    }
}

Надеюсь, это будет полезно :)

Дэниел Сэндберг
источник
6
+1 за включение блокировки, которая отсутствует в других ответах.
ctacke
1
Какая польза от объекта _lock?
Lode Vlaeminck
2
@LodeVlaeminck предотвращает изменение значения свойства во время обработки события.
Дэвид Суарес,
ИМХО, это странное место для замка. [Если только блокировка не используется где-либо еще, что является другой ситуацией.] Если два разных потока находятся в состоянии гонки для установки общего свойства, то «конечное» состояние свойства не является детерминированным. Вместо этого используйте некоторый шаблон, в котором один поток «владеет» свойством, и только они его устанавливают. КАКАЯ картина зависит от ситуации. (Если действительно нужно сменить владельца между потоками, передайте жезл / токен.) Если бы я столкнулся с необходимостью блокировки здесь, я бы внимательно изучил общий дизайн. OTOH, блокировка здесь безвредна.
ToolmakerSteve
13

просто используйте свойство

int  _theVariable;
public int TheVariable{
  get{return _theVariable;}
  set{
    _theVariable = value; 
    if ( _theVariable == 1){
      //Do stuff here.
    }
  }
}
Рассел Тройвест
источник
0

вы можете использовать общий класс:

class Wrapped<T>  {
    private T _value;

    public Action ValueChanged;

    public T Value
    {
        get => _value;

        set
        {
            OnValueChanged();
            _value = value;
        }
    }

    protected virtual void OnValueChanged() => ValueChanged?.Invoke() ;
}

и сможет делать следующее:

var i = new Wrapped<int>();

i.ValueChanged += () => { Console.WriteLine("changed!"); };

i.Value = 10;
i.Value = 10;
i.Value = 10;
i.Value = 10;

Console.ReadKey();

результат:

changed!
changed!
changed!
changed!
changed!
changed!
changed!
Андрей
источник