当变量的值改变时如何触发事件?

121

我正在使用Visual Studio以C#语言创建一个应用程序。当变量的值为1时,我希望创建一些代码来执行某个操作。

我知道我可以使用if语句来实现,但问题是该值将在异步过程中更改,因此在该值更改之前,if语句可能会被忽略。

是否可以创建事件处理程序,以便在变量值更改时触发事件?如果可以,如何做到这一点?

完全有可能我误解了if语句的工作原理!非常感谢您的任何帮助。


1
只是为了明确,观察变量的更改仅适用于您拥有的变量(或已经与IObservable/INotifyPropertyChanged/事件相关联)。如果未设计为可观察,则无法观察到系统变量的更改。 - Cœur
6个回答

158

看起来你想要创建一个属性。

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

private int _myProperty;

这允许你在属性值改变时运行一些代码。如果你愿意,你可以在这里触发一个事件。


76
您可以使用属性设置器,在字段值将要更改时引发事件。
您可以拥有自己的 EventHandler 委托,或者使用著名的 System.EventHandler 委托。
通常有一种模式:
1. 定义一个公共事件,并为其定义一个事件处理程序委托(该委托具有类型为 EventArgs 的参数)。 2. 定义一个受保护的虚方法,称为 OnXXXXX(例如 OnMyPropertyValueChanged)。在此方法中,您应该检查事件处理程序委托是否为 null,如果不是,则可以调用它(这意味着一个或多个方法已附加到事件委托)。 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窗体,则可以使用BackgroundWorker在并行线程中轻松进行操作。


3
整个互联网上最好的解释之一。我觉得我终于理解了自定义事件处理。感谢这篇文章。 - user2225495

46
.NET框架实际上提供了一个接口,您可以使用它来通知订阅者属性已更改:System.ComponentModel.INotifyPropertyChanged。此接口具有一个事件PropertyChanged。通常在WPF中用于绑定,但我发现它在业务层中作为一种标准化的属性更改通知方式非常有用。
就线程安全而言,我会在setter下面放置一个锁定以避免出现任何竞争条件。
以下是我的代码想法 :) :
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
        }
    }
}
希望这对你有所帮助 :)

7
其他答案忽略了这个锁,我支持它的加入。 - ctacke
1
对象 _lock 的用途是什么? - Lode Vlaeminck
2
@LodeVlaeminck 它可以防止在事件处理过程中更改属性的值。 - David Suarez
1
在我看来,这是一个奇怪的地方放置锁。(除非锁也在其他地方使用,那就是另一种情况了。)如果两个不同的线程处于竞争状态以设置共享属性,则该属性的“最终”状态不是确定性的。相反,使用某些模式,其中一个线程“拥有”该属性,并且只有他们才能设置它。哪种模式取决于具体情况。(如果真的需要在线程之间更改所有权,请传递接力棒/令牌。)如果我在这里遇到需要锁定的情况,我会仔细检查整体设计。另一方面,在这里使用锁是无害的。 - ToolmakerSteve

13

只需使用一个属性即可。

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

5

2022

您可以使用通用类:

class Wrapped<T>  {
    private T _value;

    public Action WillChange;
    public Action DidChange;

    public T Value
    {
        get => _value;

        set
        {
            if ( _value != value )
            {
                OnWillChange();
                _value = value;
                OnDidChanged();
            }
        }
    }

    protected virtual void OnWillChange() => WillChange?.Invoke();
    protected virtual void OnDidChange() => DidChange?.Invoke();
}

并且将能够执行以下操作:

var i = new Wrapped<int>();

i.WillChange += () => { Console.WriteLine("will be changed!"); };
i.DidChange += () => { Console.WriteLine("changed!"); };

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

Console.ReadKey();

结果:

will be changed!
changed!
will be changed!
changed!
will be changed!
changed!
will be changed!
changed!

2
我个人不喜欢在更改值之前调用 OnValueChanged();。这似乎是一种编程错误,会破坏你的代码。 - Zun
3
在触发变更事件之前,我个人会先检查新值是否与旧值不同。 - michasaurus

1
一个简单的方法涉及使用变量的get和set函数。

    using System;
    public string Name{
    get{
     return name;
    }
    
    set{
     name= value;
     OnVarChange?.Invoke();
    }
    }
    private string name;
    
    public event System.Action OnVarChange;


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接