จะทริกเกอร์เหตุการณ์เมื่อค่าของตัวแปรเปลี่ยนแปลงได้อย่างไร


97

ฉันกำลังสร้างแอปพลิเคชันใน C # โดยใช้ Visual Studio ฉันต้องการสร้างโค้ดเพื่อที่ว่าเมื่อตัวแปรมีค่าเป็น 1 โค้ดส่วนหนึ่งจะถูกนำออกมา ฉันรู้ว่าฉันสามารถใช้คำสั่ง if ได้ แต่ปัญหาคือค่าจะถูกเปลี่ยนแปลงในกระบวนการอะซิงโครนัสดังนั้นในทางเทคนิคคำสั่ง if อาจถูกละเว้นก่อนที่ค่าจะเปลี่ยนไป

เป็นไปได้หรือไม่ที่จะสร้างตัวจัดการเหตุการณ์เพื่อที่เมื่อค่าตัวแปรเปลี่ยนแปลงเหตุการณ์จะถูกทริกเกอร์ ถ้าเป็นเช่นนั้นฉันจะทำอย่างไร

เป็นไปได้อย่างสมบูรณ์ว่าฉันอาจเข้าใจผิดว่าคำสั่ง if ทำงานอย่างไร! ความช่วยเหลือใด ๆ จะได้รับการชื่นชมมาก


1
เพื่อความชัดเจนการสังเกตการเปลี่ยนแปลงของตัวแปรนั้นเป็นไปได้สำหรับตัวแปรที่คุณเป็นเจ้าของเท่านั้น (หรือซึ่งเป็น IObservable / INotifyPropertyChanged / Event-related) คุณไม่สามารถสังเกตการเปลี่ยนแปลงตัวแปรของระบบได้หากไม่ได้ออกแบบมาให้สังเกตได้
Cœur

คำตอบ:


125

สำหรับฉันดูเหมือนว่าคุณต้องการสร้างอสังหาริมทรัพย์

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

private int _myProperty;

สิ่งนี้ช่วยให้คุณสามารถรันโค้ดได้ทุกครั้งที่ค่าคุณสมบัติเปลี่ยนแปลง คุณสามารถจัดงานได้ที่นี่ถ้าคุณต้องการ


67

คุณสามารถใช้ตัวตั้งค่าคุณสมบัติเพื่อเพิ่มเหตุการณ์เมื่อใดก็ตามที่ค่าของเขตข้อมูลกำลังจะเปลี่ยนไป

คุณสามารถมีผู้รับมอบสิทธิ์ 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 ที่คุณ สามารถใช้ BackgourndWorker เพื่อทำสิ่งต่างๆในเธรดคู่ขนานได้ดีและง่ายดาย


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 จะป้องกันการเปลี่ยนค่าของคุณสมบัติในขณะที่เหตุการณ์กำลังดำเนินการ
David Suarez

IMHO นี่เป็นสถานที่แปลกสำหรับการล็อค [เว้นแต่จะใช้การล็อกที่อื่นด้วยซึ่งเป็นสถานการณ์ที่แตกต่างกัน] หากเธรดที่แตกต่างกันสองเธรดอยู่ในเงื่อนไขการแข่งขันเพื่อตั้งค่าคุณสมบัติที่ใช้ร่วมกันสถานะ "สุดท้าย" ของคุณสมบัติจะไม่ถูกกำหนด แทนที่จะใช้รูปแบบบางอย่างที่เธรดหนึ่ง "เป็นเจ้าของ" คุณสมบัติและมีเพียงรูปแบบที่ตั้ง รูปแบบไหนขึ้นอยู่กับสถานการณ์ (หากจำเป็นต้องเปลี่ยนความเป็นเจ้าของระหว่างเธรดจริงๆให้ส่งกระบอง / โทเค็น) หากฉันพบว่าจำเป็นต้องล็อกที่นี่ฉันจะตรวจสอบการออกแบบโดยรวมอย่างละเอียด OTOH ล็อคที่นี่ไม่เป็นอันตราย
ToolmakerSteve


1

วิธีการง่ายๆคือการใช้ฟังก์ชัน get and set บนตัวแปร


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


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!
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.