¿Cómo desencadenar un evento cuando se cambia el valor de una variable?

96

Actualmente estoy creando una aplicación en C # usando Visual Studio. Quiero crear un código para que cuando una variable tenga un valor de 1, se lleve a cabo una determinada pieza de código. Sé que puedo usar una declaración if, pero el problema es que el valor se cambiará en un proceso asincrónico, por lo que técnicamente la declaración if podría ignorarse antes de que el valor haya cambiado.

¿Es posible crear un controlador de eventos para que cuando el valor de la variable cambie, se active un evento? Si es así, ¿cómo puedo hacer esto?

¡Es completamente posible que haya entendido mal cómo funciona una declaración if! Cualquier ayuda será muy apreciada.

James Mundy
fuente
1
Para ser claros, observar el cambio de una variable solo es posible para una variable de su propiedad (o que ya es IObservable / INotifyPropertyChanged / Event-related). No puede observar un cambio de variable del sistema si no fue diseñado para ser observado.
Cœur

Respuestas:

123

Me parece que quieres crear una propiedad.

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

private int _myProperty;

Esto le permite ejecutar código cada vez que cambia el valor de la propiedad. Podrías organizar un evento aquí, si quisieras.

Jonathan Wood
fuente
68

Puede usar un establecedor de propiedades para generar un evento siempre que cambie el valor de un campo.

Puede tener su propio delegado EventHandler o puede utilizar el famoso delegado System.EventHandler.

Por lo general, hay un patrón para esto:

  1. Defina un evento público con un delegado de controlador de eventos (que tenga un argumento de tipo EventArgs).
  2. Defina un método virtual protegido llamado OnXXXXX (OnMyPropertyValueChanged, por ejemplo). En este método, debe verificar si el delegado del controlador de eventos es nulo y, en caso contrario, puede llamarlo (significa que hay uno o más métodos adjuntos a la delegación de eventos).
  3. Llame a este método protegido siempre que desee notificar a los suscriptores que algo ha cambiado.

Aquí hay un ejemplo

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

La ventaja de este enfoque es que permite que cualquier otra clase que desee heredar de su clase cambie el comportamiento si es necesario.

Si desea capturar un evento en un subproceso diferente que se está generando, debe tener cuidado de no cambiar el estado de los objetos que están definidos en otro subproceso, lo que provocará que se lance una excepción de subproceso cruzado. Para evitar esto, puede usar un método Invoke en el objeto cuyo estado desea cambiar para asegurarse de que el cambio está ocurriendo en el mismo hilo en el que se generó el evento o en caso de que esté tratando con un formulario de Windows. puede usar un BackgourndWorker para hacer cosas en un hilo paralelo agradable y fácil.

Beatles1692
fuente
3
Una de las mejores explicaciones de toda la Web. Creo que finalmente estoy entendiendo el manejo de eventos personalizados. Agradecido por esta publicación.
Adiós
43

El marco .NET en realidad proporciona una interfaz que puede usar para notificar a los suscriptores cuando una propiedad ha cambiado: System.ComponentModel.INotifyPropertyChanged. Esta interfaz tiene un evento PropertyChanged. Se usa generalmente en WPF para la vinculación, pero lo he encontrado útil en capas comerciales como una forma de estandarizar la notificación de cambios de propiedad.

En términos de seguridad del hilo, pondría un candado en el colocador para que no se encuentre con ninguna condición de carrera.

Aquí están mis pensamientos en 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 esto sea útil :)

Daniel Sandberg
fuente
6
+1 para la inclusión del candado que omiten las otras respuestas.
ctacke
1
¿Cuál es el uso del objeto _lock?
Lode Vlaeminck
2
@LodeVlaeminck evita cambiar el valor de la propiedad mientras se procesa el evento.
David Suarez
En mi humilde opinión, este es un lugar extraño para un candado. [A menos que el bloqueo también se utilice en otro lugar, que es una situación diferente.] Si dos subprocesos diferentes están en una condición de carrera para establecer una propiedad compartida, entonces el estado "final" de la propiedad no es determinista. En su lugar, utilice algún patrón en el que un subproceso "posea" la propiedad y solo ellos la establezcan. CUAL patrón depende de la situación. (Si realmente necesita cambiar la propiedad entre subprocesos, pase un testigo / token). Si encontrara la necesidad de un candado aquí, examinaría cuidadosamente el diseño general. OTOH, un candado aquí es inofensivo.
ToolmakerSteve
13

solo usa una propiedad

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

puedes usar una clase 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() ;
}

y podrá hacer lo siguiente:

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!
Andrés
fuente