Como acionar o evento quando o valor de uma variável é alterado?

96

Atualmente, estou criando um aplicativo em C # usando o Visual Studio. Quero criar um código para que, quando uma variável tiver o valor 1, um determinado trecho de código seja executado. Eu sei que posso usar uma instrução if, mas o problema é que o valor será alterado em um processo assíncrono, portanto, tecnicamente, a instrução if pode ser ignorada antes que o valor seja alterado.

É possível criar um manipulador de eventos para que, quando o valor da variável muda, um evento seja disparado? Se sim, como posso fazer isso?

É completamente possível que eu pudesse ter entendido mal como uma instrução if funciona! Qualquer ajuda seria muito apreciada.

James Mundy
fonte
1
Só para ficar claro, observar a mudança de uma variável só é possível para uma variável que você possui (ou que já é IObservable / INotifyPropertyChanged / Event-related). Você não pode observar uma mudança de variável do sistema se ela não foi projetada para ser observada.
Coeur

Respostas:

123

Parece-me que você deseja criar uma propriedade.

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

private int _myProperty;

Isso permite que você execute algum código sempre que o valor da propriedade for alterado. Você pode criar um evento aqui, se quiser.

Jonathan Wood
fonte
68

Você pode usar um configurador de propriedade para gerar um evento sempre que o valor de um campo for alterado.

Você pode ter seu próprio delegado EventHandler ou pode usar o famoso delegado System.EventHandler.

Normalmente, há um padrão para isso:

  1. Defina um evento público com um delegado de manipulador de eventos (que tem um argumento do tipo EventArgs).
  2. Defina um método virtual protegido chamado OnXXXXX (OnMyPropertyValueChanged por exemplo). Neste método, você deve verificar se o delegado do manipulador de eventos é nulo e, caso contrário, você pode chamá-lo (isso significa que há um ou mais métodos anexados à delegação de eventos).
  3. Chame esse método protegido sempre que quiser notificar os assinantes de que algo mudou.

Aqui está um exemplo

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

A vantagem dessa abordagem é que você permite que qualquer outra classe que deseja herdar de sua classe altere o comportamento, se necessário.

Se você quiser capturar um evento em um thread diferente que está sendo gerado, deve ter cuidado para não alterar o estado dos objetos que são definidos em outro thread, o que fará com que uma exceção de thread cruzado seja lançada. Para evitar isso, você pode usar um método Invoke no objeto cujo estado deseja alterar para ter certeza de que a mudança está acontecendo no mesmo segmento em que o evento foi gerado ou no caso de estar lidando com um Windows Form, pode usar um BackgourndWorker para fazer coisas em um thread paralelo de forma fácil e agradável.

Beatles1692
fonte
3
Uma das melhores explicações de toda a web. Acho que finalmente estou entendendo o tratamento de eventos personalizados. Grato por este post.
Adeus de
43

A estrutura .NET realmente fornece uma interface que você pode usar para notificar os assinantes quando uma propriedade for alterada: System.ComponentModel.INotifyPropertyChanged. Esta interface possui um evento PropertyChanged. Geralmente é usado no WPF para vinculação, mas descobri que é útil em camadas de negócios como uma forma de padronizar a notificação de alteração de propriedade.

Em termos de segurança de thread, eu colocaria um travamento no setter para que você não entre em nenhuma condição de corrida.

Aqui estão meus pensamentos sobre o código :):

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

Espero que isso seja útil :)

Daniel Sandberg
fonte
6
+1 para a inclusão do cadeado que as demais respostas omitem.
ctacke
1
Qual é a utilidade do objeto _lock?
Lode Vlaeminck
2
@LodeVlaeminck evita a alteração do valor da propriedade enquanto o evento está sendo processado.
David Suarez de
IMHO, este é um lugar estranho para uma fechadura. [A menos que o bloqueio também seja usado em outro lugar, o que é uma situação diferente.] Se dois threads diferentes estão em uma condição de corrida para definir uma propriedade compartilhada, o estado "final" da propriedade não é determinístico. Em vez disso, use algum padrão onde um thread "possui" a propriedade e somente eles a definem. QUAL padrão depende da situação. (Se realmente precisar alterar a propriedade entre os threads, passe um bastão / token.) Se eu encontrasse a necessidade de um bloqueio aqui, examinaria cuidadosamente o design geral. OTOH, uma fechadura aqui é inofensiva.
Toolmaker Steve
13

apenas use uma propriedade

int  _theVariable;
public int TheVariable{
  get{return _theVariable;}
  set{
    _theVariable = value; 
    if ( _theVariable == 1){
      //Do stuff here.
    }
  }
}
Russell Troywest
fonte
0

você pode usar uma classe genérica:

class Wrapped<T>  {
    private T _value;

    public Action ValueChanged;

    public T Value
    {
        get => _value;

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

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

e será capaz de fazer o seguinte:

var i = new Wrapped<int>();

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

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

Console.ReadKey();

resultado:

changed!
changed!
changed!
changed!
changed!
changed!
changed!
Andrew
fonte