Jak wywołać zdarzenie po zmianie wartości zmiennej?

96

Obecnie tworzę aplikację w języku C # przy użyciu programu Visual Studio. Chcę utworzyć kod, aby gdy zmienna miała wartość 1, wykonywany był określony fragment kodu. Wiem, że mogę użyć instrukcji if, ale problem polega na tym, że wartość zostanie zmieniona w procesie asynchronicznym, więc technicznie rzecz biorąc, instrukcja if może zostać zignorowana, zanim wartość ulegnie zmianie.

Czy można utworzyć procedurę obsługi zdarzenia, aby po zmianie wartości zmiennej wyzwalane było zdarzenie? Jeśli tak, jak mogę to zrobić?

Jest całkowicie możliwe, że mogłem źle zrozumieć, jak działa instrukcja if! Każda pomoc byłaby mile widziana.

James Mundy
źródło
1
Dla jasności obserwowanie zmiany zmiennej jest możliwe tylko w przypadku zmiennej, której jesteś właścicielem (lub która jest już związana z IObservable / INotifyPropertyChanged / Event). Nie można zaobserwować zmiany zmiennej systemowej, jeśli nie została zaprojektowana do obserwacji.
Cœur,

Odpowiedzi:

124

Wydaje mi się, że chcesz stworzyć nieruchomość.

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

private int _myProperty;

Pozwala to na uruchomienie kodu za każdym razem, gdy zmienia się wartość właściwości. Jeśli chcesz, możesz zorganizować tutaj wydarzenie.

Jonathan Wood
źródło
68

Możesz użyć metody ustawiającej właściwości, aby zgłosić zdarzenie za każdym razem, gdy wartość pola ulegnie zmianie.

Możesz mieć własnego delegata EventHandler lub możesz użyć słynnego delegata System.EventHandler.

Zwykle jest na to wzór:

  1. Zdefiniuj zdarzenie publiczne z delegatem programu obsługi zdarzeń (który ma argument typu EventArgs).
  2. Zdefiniuj chronioną metodę wirtualną o nazwie OnXXXXX (na przykład OnMyPropertyValueChanged). W tej metodzie należy sprawdzić, czy delegat obsługi zdarzeń ma wartość NULL, a jeśli nie, można go wywołać (oznacza to, że do delegacji zdarzenia jest dołączona jedna lub więcej metod).
  3. Wywołaj tę chronioną metodę, gdy chcesz powiadomić subskrybentów, że coś się zmieniło.

Oto przykład

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

Zaletą tego podejścia jest to, że pozwalasz innym klasom, które chcą dziedziczyć z Twojej klasy, na zmianę zachowania, jeśli to konieczne.

Jeśli chcesz złapać zdarzenie w innym wątku, który jest wywoływany, musisz uważać, aby nie zmienić stanu obiektów, które są zdefiniowane w innym wątku, co spowoduje zgłoszenie wyjątku między wątkami. Aby tego uniknąć, możesz użyć metody Invoke na obiekcie, który chcesz zmienić jego stan, aby upewnić się, że zmiana zachodzi w tym samym wątku, w którym zostało zgłoszone zdarzenie, lub w przypadku, gdy masz do czynienia z formularzem systemu Windows może używać BackgourndWorker do wykonywania czynności w równoległym wątku w łatwy i przyjemny sposób.

Beatles1692
źródło
3
Jedno z najlepszych wyjaśnień w całej sieci. Myślę, że w końcu rozumiem obsługę zdarzeń niestandardowych. Wdzięczny za ten post.
Żegnaj
44

W rzeczywistości platforma .NET zapewnia interfejs, którego można używać do powiadamiania subskrybentów o zmianie właściwości: System.ComponentModel.INotifyPropertyChanged. Ten interfejs ma jedno zdarzenie PropertyChanged. Jest zwykle używany w WPF do wiązania, ale uważam, że jest przydatny w warstwach biznesowych jako sposób na standaryzację powiadamiania o zmianie właściwości.

Jeśli chodzi o bezpieczeństwo nici, założyłbym blokadę w seterze, abyś nie napotkał żadnych warunków wyścigu.

Oto moje przemyślenia w kodzie :):

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

Mam nadzieję, że to jest pomocne :)

Daniel Sandberg
źródło
6
+1 za włączenie blokady, którą pomijają inne odpowiedzi.
ctacke
1
Jaki jest pożytek z obiektu _lock?
Lode Vlaeminck
2
@LodeVlaeminck zapobiega zmianie wartości właściwości podczas przetwarzania zdarzenia.
David Suarez
IMHO, to dziwne miejsce na zamek. [O ile blokada nie jest również używana gdzie indziej, co jest inną sytuacją]. Jeśli dwa różne wątki są w wyścigu, aby ustawić współdzieloną właściwość, wówczas „ostateczny” stan właściwości nie jest deterministyczny. Zamiast tego użyj wzorca, w którym jeden wątek „posiada” właściwość i tylko on ją ustawia. KTÓRY wzór zależy od sytuacji. (Jeśli naprawdę trzeba zmienić własność między wątkami, przekaż pałeczkę / token.) Gdybym napotkał tutaj potrzebę blokady, dokładnie przeanalizowałbym ogólny projekt. OTOH, zamek tutaj jest nieszkodliwy.
ToolmakerSteve,
13

po prostu użyj właściwości

int  _theVariable;
public int TheVariable{
  get{return _theVariable;}
  set{
    _theVariable = value; 
    if ( _theVariable == 1){
      //Do stuff here.
    }
  }
}
Russell Troywest
źródło
0

możesz użyć klasy ogólnej:

class Wrapped<T>  {
    private T _value;

    public Action ValueChanged;

    public T Value
    {
        get => _value;

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

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

i będzie w stanie wykonać następujące czynności:

var i = new Wrapped<int>();

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

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

Console.ReadKey();

wynik:

changed!
changed!
changed!
changed!
changed!
changed!
changed!
Andrzej
źródło