【问题标题】:How do I raise an event via reflection in .NET/C#?如何通过 .NET/C# 中的反射引发事件?
【发布时间】:2008-10-13 18:31:30
【问题描述】:

我有一个第三方编辑器,它基本上包含一个文本框和一个按钮(DevExpress ButtonEdit 控件)。我想让特定的击键 (Alt + Down) 模拟单击​​按钮。为了避免一遍又一遍地写这个,我想制作一个通用的 KeyUp 事件处理程序,它将引发 ButtonClick 事件。不幸的是,控件中似乎没有引发 ButtonClick 事件的方法,所以...

如何通过反射从外部函数引发事件?

【问题讨论】:

    标签: c# .net event-handling devexpress


    【解决方案1】:

    这是一个使用泛型的演示(省略了错误检查):

    using System;
    using System.Reflection;
    static class Program {
      private class Sub {
        public event EventHandler<EventArgs> SomethingHappening;
      }
      internal static void Raise<TEventArgs>(this object source, string eventName, TEventArgs eventArgs) where TEventArgs : EventArgs
      {
        var eventDelegate = (MulticastDelegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source);
        if (eventDelegate != null)
        {
          foreach (var handler in eventDelegate.GetInvocationList())
          {
            handler.Method.Invoke(handler.Target, new object[] { source, eventArgs });
          }
        }
      }
      public static void Main()
      {
        var p = new Sub();
        p.Raise("SomethingHappening", EventArgs.Empty);
        p.SomethingHappening += (o, e) => Console.WriteLine("Foo!");
        p.Raise("SomethingHappening", EventArgs.Empty);
        p.SomethingHappening += (o, e) => Console.WriteLine("Bar!");
        p.Raise("SomethingHappening", EventArgs.Empty);
        Console.ReadLine();
      }
    }
    

    【讨论】:

    • 我注意到你创建了一个 eventInfo 局部变量,但你从来没有对它做任何事情。是我一个人还是可以删除第一行?
    • 您不需要获取 eventInfo。这是没用的。否则,很好的样本!
    • 我认为没有必要调用 GetInvocationList()。只需调用eventDelegate就足够了。
    • eventDelegate.DynamicInvoke(args) 正如@HappyNomad 所说的那样工作得很好。
    • 你不使用 eventInfo
    【解决方案2】:

    一般来说,你不能。将事件视为基本上成对的AddHandler/RemoveHandler 方法(因为它们基本上就是这样)。它们的实现方式取决于班级。大多数 WinForms 控件使用EventHandlerList 作为它们的实现,但是如果它开始获取私有字段和键,您的代码将非常脆弱。

    ButtonEdit 控件是否公开了您可以调用的OnClick 方法?

    脚注:实际上,事件可以有“raise”成员,因此EventInfo.GetRaiseMethod。但是,C# 从未填充过它,而且我也不相信它在一般框架中。

    【讨论】:

      【解决方案3】:

      您通常不能引发其他类事件。事件实际上存储为私有委托字段,外加两个访问器(add_event 和 remove_event)。

      要通过反射来实现,您只需找到私有委托字段,获取它,然后调用它。

      【讨论】:

        【解决方案4】:

        我写了一个类的扩展,它实现了 INotifyPropertyChanged 来注入 RaisePropertyChange 方法,所以我可以这样使用它:

        this.RaisePropertyChanged(() => MyProperty);
        

        没有在任何基类中实现该方法。对于我的使用来说它很慢,但也许源代码可以帮助某人。

        原来是这样:

        using System;
        using System.Collections.Generic;
        using System.ComponentModel;
        using System.Diagnostics;
        using System.Linq.Expressions;
        using System.Reflection;
        using System.Globalization;
        
        namespace Infrastructure
        {
            /// <summary>
            /// Adds a RaisePropertyChanged method to objects implementing INotifyPropertyChanged.
            /// </summary>
            public static class NotifyPropertyChangeExtension
            {
                #region private fields
        
                private static readonly Dictionary<string, PropertyChangedEventArgs> eventArgCache = new Dictionary<string, PropertyChangedEventArgs>();
                private static readonly object syncLock = new object();
        
                #endregion
        
                #region the Extension's
        
                /// <summary>
                /// Verifies the name of the property for the specified instance.
                /// </summary>
                /// <param name="bindableObject">The bindable object.</param>
                /// <param name="propertyName">Name of the property.</param>
                [Conditional("DEBUG")]
                public static void VerifyPropertyName(this INotifyPropertyChanged bindableObject, string propertyName)
                {
                    bool propertyExists = TypeDescriptor.GetProperties(bindableObject).Find(propertyName, false) != null;
                    if (!propertyExists)
                        throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
                            "{0} is not a public property of {1}", propertyName, bindableObject.GetType().FullName));
                }
        
                /// <summary>
                /// Gets the property name from expression.
                /// </summary>
                /// <param name="notifyObject">The notify object.</param>
                /// <param name="propertyExpression">The property expression.</param>
                /// <returns>a string containing the name of the property.</returns>
                public static string GetPropertyNameFromExpression<T>(this INotifyPropertyChanged notifyObject, Expression<Func<T>> propertyExpression)
                {
                    return GetPropertyNameFromExpression(propertyExpression);
                }
        
                /// <summary>
                /// Raises a property changed event.
                /// </summary>
                /// <typeparam name="T"></typeparam>
                /// <param name="bindableObject">The bindable object.</param>
                /// <param name="propertyExpression">The property expression.</param>
                public static void RaisePropertyChanged<T>(this INotifyPropertyChanged bindableObject, Expression<Func<T>> propertyExpression)
                {
                    RaisePropertyChanged(bindableObject, GetPropertyNameFromExpression(propertyExpression));
                }
        
                #endregion
        
                /// <summary>
                /// Raises the property changed on the specified bindable Object.
                /// </summary>
                /// <param name="bindableObject">The bindable object.</param>
                /// <param name="propertyName">Name of the property.</param>
                private static void RaisePropertyChanged(INotifyPropertyChanged bindableObject, string propertyName)
                {
                    bindableObject.VerifyPropertyName(propertyName);
                    RaiseInternalPropertyChangedEvent(bindableObject, GetPropertyChangedEventArgs(propertyName));
                }
        
                /// <summary>
                /// Raises the internal property changed event.
                /// </summary>
                /// <param name="bindableObject">The bindable object.</param>
                /// <param name="eventArgs">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
                private static void RaiseInternalPropertyChangedEvent(INotifyPropertyChanged bindableObject, PropertyChangedEventArgs eventArgs)
                {
                    // get the internal eventDelegate
                    var bindableObjectType = bindableObject.GetType();
        
                    // search the base type, which contains the PropertyChanged event field.
                    FieldInfo propChangedFieldInfo = null;
                    while (bindableObjectType != null)
                    {
                        propChangedFieldInfo = bindableObjectType.GetField("PropertyChanged", BindingFlags.Instance | BindingFlags.NonPublic);
                        if (propChangedFieldInfo != null)
                            break;
        
                        bindableObjectType = bindableObjectType.BaseType;
                    }
                    if (propChangedFieldInfo == null)
                        return;
        
                    // get prop changed event field value
                    var fieldValue = propChangedFieldInfo.GetValue(bindableObject);
                    if (fieldValue == null)
                        return;
        
                    MulticastDelegate eventDelegate = fieldValue as MulticastDelegate;
                    if (eventDelegate == null)
                        return;
        
                    // get invocation list
                    Delegate[] delegates = eventDelegate.GetInvocationList();
        
                    // invoke each delegate
                    foreach (Delegate propertyChangedDelegate in delegates)
                        propertyChangedDelegate.Method.Invoke(propertyChangedDelegate.Target, new object[] { bindableObject, eventArgs });
                }
        
                /// <summary>
                /// Gets the property name from an expression.
                /// </summary>
                /// <param name="propertyExpression">The property expression.</param>
                /// <returns>The property name as string.</returns>
                private static string GetPropertyNameFromExpression<T>(Expression<Func<T>> propertyExpression)
                {
                    var lambda = (LambdaExpression)propertyExpression;
        
                    MemberExpression memberExpression;
        
                    if (lambda.Body is UnaryExpression)
                    {
                        var unaryExpression = (UnaryExpression)lambda.Body;
                        memberExpression = (MemberExpression)unaryExpression.Operand;
                    }
                    else memberExpression = (MemberExpression)lambda.Body;
        
                    return memberExpression.Member.Name;
                }
        
                /// <summary>
                /// Returns an instance of PropertyChangedEventArgs for the specified property name.
                /// </summary>
                /// <param name="propertyName">
                /// The name of the property to create event args for.
                /// </param>
                private static PropertyChangedEventArgs GetPropertyChangedEventArgs(string propertyName)
                {
                    PropertyChangedEventArgs args;
        
                    lock (NotifyPropertyChangeExtension.syncLock)
                    {
                        if (!eventArgCache.TryGetValue(propertyName, out args))
                            eventArgCache.Add(propertyName, args = new PropertyChangedEventArgs(propertyName));
                    }
        
                    return args;
                }
            }
        }
        

        我删除了原始代码的某些部分,因此扩展程序应该可以按原样运行,而无需引用我的库的其他部分。但它并没有真正经过测试。

        附:代码的某些部分是从其他人那里借来的。真可惜,我忘记了我是从哪里得到它的。 :(

        【讨论】:

        • 非常感谢。实际上,您的答案是这里唯一进行继承树调查和正确的空值检查的答案。
        • 为什么要缓存事件参数?恕我直言,所需的行李,字典和锁,超过了任何潜在的性能提升。我错过了什么(共享状态等)吗?
        • 您应该在此处查看解决方案 1stackoverflow.com/a/54789191/7108481
        【解决方案5】:

        来自Raising an event via reflection,虽然我认为VB.NET 中的答案,即在此之前的两个帖子将为您提供通用方法(例如,我希望VB.NET 中引用不在同一类中的类型的灵感):

         public event EventHandler<EventArgs> MyEventToBeFired;
        
            public void FireEvent(Guid instanceId, string handler)
            {
        
                // Note: this is being fired from a method with in the same
                //       class that defined the event (that is, "this").
        
                EventArgs e = new EventArgs(instanceId);
        
                MulticastDelegate eventDelagate =
                      (MulticastDelegate)this.GetType().GetField(handler,
                       System.Reflection.BindingFlags.Instance |
                       System.Reflection.BindingFlags.NonPublic).GetValue(this);
        
                Delegate[] delegates = eventDelagate.GetInvocationList();
        
                foreach (Delegate dlg in delegates)
                {
                    dlg.Method.Invoke(dlg.Target, new object[] { this, e });
                }
            }
        
            FireEvent(new Guid(),  "MyEventToBeFired");
        

        【讨论】:

        • 这个例子帮助了我。我需要在我的 WPF 应用程序中的 Windows 应用程序对象中引发 CLR 事件,并且需要通过反射来引用它。因此,我在 FireEvent 方法中使用了 Application.Current 而不是这个。很好的提示,根据需要更改 FireEvent 方法的绑定标志。
        【解决方案6】:

        事实证明,我可以做到这一点,但没有意识到:

        buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down);
        

        但如果我不能,我将不得不深入研究源代码并找到引发事件的方法。

        谢谢大家的帮助。

        【讨论】:

          【解决方案7】:

          Wiebe Cnossen 的accepted answer 中的代码似乎可以简化为:

          private void RaiseEventViaReflection(object source, string eventName)
          {
              ((Delegate)source
                  .GetType()
                  .GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic)
                  .GetValue(source))
                  .DynamicInvoke(source, EventArgs.Empty);
          }
          

          【讨论】:

            【解决方案8】:

            如果你知道控件是一个按钮,你可以调用它的PerformClick() 方法。对于OnEnterOnExit 等其他事件,我也有类似的问题。如果我不想为每个控件类型派生一个新类型,我就无法引发这些事件。

            【讨论】:

            • '执行点击'?你一定是在开玩笑吧,太棒了!
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-07-11
            • 1970-01-01
            • 2015-10-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多