【问题标题】:Firing an event / function on a property? (C#)在属性上触发事件/函数? (C#)
【发布时间】:2009-08-06 10:40:22
【问题描述】:

我正在使用一个我无法编辑的类,它有一个属性(一个布尔值),当它发生变化时很高兴收到通知,我在导入类时无法编辑获取或设置的属性来自 .dll(我没有代码)。

如何创建在属性更改时触发的事件/函数?

附加
它仅在其自己的类中更改,直接更改为底层私有变量。

例如

private bool m_MyValue = false;

public bool MyValue
  {
  get { return m_MyValue; }
  }

private void SomeFunction()
  {
  m_MyValue = true;
  }

【问题讨论】:

    标签: c# .net custom-event


    【解决方案1】:

    你不能,基本上......不能不使用调试器 API 在执行时注入代码并修改原始库的 IL(我不推荐其中任何一种解决方案;除了其他任何东西它可能违反图书馆的许可)。

    基本上,如果一个属性不支持通知,它就不支持通知。你应该寻找一种不同的方式来解决你的问题。 (例如,投票会起作用吗?)

    【讨论】:

    • 我想我将不得不沿着轮询路线走,这不是我希望的,因为在轮询时间期间数据可能是冗余的(我想在数据被使用之前捕获数据)其他功能)
    • 或用您自己的支持 INotify 的类或您自己的实现来包装该类?
    • @Andy:这取决于。如果你这样做了,那么你就不能使用这个类作为原始类型,这可能是代码的其他位所需要的。
    【解决方案2】:

    您不能直接执行此操作 [正如 Jon Skeet 所说],除非它是虚拟的,否则您可以拦截该类的所有实例创建,并且不会更改影响真正“价值”的支持字段道具。

    暴力破解的唯一方法是使用 Mono.Cecil 或 MS CCI 来检测 prop setter,例如 this DimeCast on Cecil。 (或 PostSharp)

    但是,这不会捕获对支持字段的内部更改(如果有的话)。 (这就是为什么包装可能不起作用)。

    更新:鉴于您肯定试图捕获底层字段更改的更新,唯一的方法是使用 PS / CCI / Cecil 并分析流程以拦截所有字段更新。总之,不太可行。

    【讨论】:

      【解决方案3】:

      可以说,做到这一点的唯一真正方法是创建某种“观察者”组件,在单独的线程中运行,其工作是每隔一段时间读取属性并在属性值更改时引发事件。当然,这个解决方案在线程和同步的混沌水域中航行。

      假设您的应用程序对于此对象是单线程的,您最简洁的解决方案是通过代理对象对该对象进行方法调用。它将负责检查属性的前后状态,并在它发生变化的情况下引发事件。

      下面是我所说的一个简单示例:

      public class SomeProxy
      {
          public SomeProxy(ExternalObject obj)
          {
               _obj = obj;
          }
      
          public event EventArgs PropertyChanged;
      
          private bool _lastValue;
      
          private ExternalObject _obj;
      
          protected virtual void OnPropertyChanged()
          {
              if(PropertyChanged != null)
                  PropertyChanged();
          }
      
          protected virtual void PreMethodCall()
          {
              _lastValue = _obj.SomeProperty;
          }
      
          protected virtual void PostMethodCall()
          {
              if(_lastValue != _obj.SomeProperty)
                  OnPropertyChanged();
          }
      
          // Proxy method.
          public SomeMethod(int arg)
          {
              PreMethodCall();
              _obj.SomeMethod(arg); // Call actual method.
              PostMethodCall();
          }
      }
      

      显然,您可以将此代理模式构建到合适的对象中 - 您只需注意 所有 调用必须通过代理才能在您期望的时候引发事件。

      【讨论】:

      • +1 获取完整示例(尽管 OP 明确表示他想将其视为黑匣子,因此答案不适合 IMNSHO)
      【解决方案4】:

      如前所述,最直接的方法(也是对代码更改最少的方法)是使用 PostSharp 等 AOP 库。

      但是,使用传统的 C#/.NET 可以实现解决方案,使用 dependency property pattern,在整个 WPF 中使用效果很好。我建议阅读此内容,并考虑在适当的情况下为您的项目实施这样的系统(或至少是其简化版本)。

      【讨论】:

        【解决方案5】:

        您需要创建一个将类包装在 dll 中的类,在 setter 属性中只需使用普通方法引发一个事件。

        【讨论】:

        • 但这不会捕获组件通过其设置器进行的内部更新,或者直接更改 p;ropget 将反映的内部状态。
        【解决方案6】:

        你能从类继承并隐藏属性吗?像这样的……

        class MyClass : BaseClass
        {
            // hide base property.
            public new bool MyProperty
            {
                get
                {
                    return base.MyProperty;
                }
        
                set
                {
                    base.MyProperty = value;
                    RaiseMyPropertyChangedEvent();
                }
            }
        }
        

        【讨论】:

        • stackoverflow.com/questions/1238137/… 相同。 (更新到 Q 意味着这不起作用)
        • @Thomas:不,'new' 修饰符关键字不要求基数是虚拟的。
        • 好的,抱歉,我没有看到“新”...但是只有通过 MyClass 类型的变量操作对象时它才会起作用。如果变量是 BaseClass 类型,则将调用基本实现,因为该属性不是多态
        • 这是属性隐藏,而不是覆盖 :)
        【解决方案7】:

        我认为 Alex 的包装器想法很好,但是,考虑到对您来说最重要的是您知道值在 使用之前已更改,您可以简单地将通知移至getter,规避内部值变化的后顾之忧。这样您就可以获得类似于轮询但可靠的信息:

        class MyClass : BaseClass
        {
            //local value
            private bool local;
        
            //first access yet?
            private bool accessed = false;
        
            // Override base property.
            public new bool MyProperty
            {
                get
                {
                    if(!accessed)
                    {
                        // modify first-get case according to your taste, e.g.
                        local = base.MyProperty;
                        accessed = true;
                        RaiseMyPropertyChangedBeforeUseEvent();
                    }
                    else
                    {
                        if(local != base.MyProperty)
                        {
                            local = base.MyProperty;
                            RaiseMyPropertyChangedBeforeUseEvent();
                        }
                    }
        
                    return local;
                }
        
                set
                {
                    base.MyProperty = value;
                }
            }
        }
        

        【讨论】:

          【解决方案8】:

          您可以尝试继承它并使用它的子代而不是它。 覆盖属性的“set”,使其引发事件。

          编辑:...仅当属性在父类中为虚拟时...

          【讨论】:

            猜你喜欢
            • 2019-08-02
            • 2018-08-24
            • 1970-01-01
            • 2017-04-27
            • 2010-09-18
            • 1970-01-01
            • 2017-10-27
            • 2011-06-01
            相关资源
            最近更新 更多