如何通过 .NET/C# 中的反射引发事件?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/198543/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 17:41:32  来源:igfitidea点击:

How do I raise an event via reflection in .NET/C#?

c#.netevent-handlingdevexpress

提问by Josh Kodroff

I have a third-party editor that basically comprises a textbox and a button (the DevExpress ButtonEdit control). I want to make a particular keystroke (Alt+ Down) emulate clicking the button. In order to avoid writing this over and over, I want to make a generic KeyUp event handler that will raise the ButtonClick event. Unfortunately, there doesn't seem to be a method in the control that raises the ButtonClick event, so...

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

How do I raise the event from an external function via reflection?

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

采纳答案by Wiebe Cnossen

Here's a demo using generics (error checks omitted):

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

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

回答by torial

From Raising an event via reflection, although I think the answer in VB.NET, that is, two posts ahead of this one will provide you with the generic approach (for example, I'd look to the VB.NET one for inspiration on referencing a type not in the same class):

通过反射引发事件,虽然我认为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");

回答by MichaelGG

You can't normally raise another classes events. Events are really stored as a private delegate field, plus two accessors (add_event and remove_event).

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

To do it via reflection, you simply need to find the private delegate field, get it, then invoke it.

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

回答by Jon Skeet

In general, you can't. Think of events as basically pairs of AddHandler/RemoveHandlermethods (as that's basically what what they are). How they're implemented is up to the class. Most WinForms controls use EventHandlerListas their implementation, but your code will be very brittle if it starts fetching private fields and keys.

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

Does the ButtonEditcontrol expose an OnClickmethod which you could call?

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

Footnote: Actually, events canhave "raise" members, hence EventInfo.GetRaiseMethod. However, this is never populated by C# and I don't believe it's in the framework in general, either.

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

回答by Josh Kodroff

As it turns out, I could do this and didn't realize it:

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

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

But if I couldn't I would've have to delve into the source code and find the method that raises the event.

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

Thanks for the help, all.

谢谢大家的帮助。

回答by Josh Kodroff

If you know that the control is a button you can call its PerformClick()method. I have similar problem for other events like OnEnter, OnExit. I can't raise those events if I don't want to derive a new type for each control type.

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

回答by ChrisTTian667

I wrote an extension to classes, which implements INotifyPropertyChanged to inject the RaisePropertyChange<T> method, so I can use it like this:

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

this.RaisePropertyChanged(() => MyProperty);

without implementing the method in any base class. For my usage it was to slow, but maybe the source code can help someone.

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

So here it is:

所以这里是:

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

I removed some parts of the original code, so the extension should work as is, without references to other parts of my library. But it's not really tested.

我删除了原始代码的某些部分,因此扩展应该按原样工作,而不引用我库的其他部分。但它并没有真正经过测试。

P.S. Some parts of the code was borrowed from someone else. Shame on me, that I forgot from where I got it. :(

PS 部分代码是从别人那里借来的。我很惭愧,我忘记了从哪里得到它。:(

回答by bitbonk

It seems that the code from the accepted answerby Wiebe Cnossen could be simplified to this:

似乎Wiebe Cnossen接受的答案中的代码可以简化为:

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