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

时间:2022-02-27 19:05:52

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?

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

8 个解决方案

#1


34  

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

#2


13  

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

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

Does the ButtonEdit control expose an OnClick method which you could call?

ButtonEdit控件公开一个可以调用的OnClick方法吗?

Footnote: Actually, events can have "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.

脚注:实际上,事件可以有“提升”成员,因此eventinfo . getraiethod。然而,c#从来没有对它进行过填充,我也不认为它在框架中是通用的。

#3


12  

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.

要通过反射完成它,只需找到private delegate字段,获取它,然后调用它。

#4


8  

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

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

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. :(

附注:代码的一些部分是从别人那里借来的。我真惭愧,我从哪儿弄到的都忘了。:(

#5


6  

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.

谢谢大家的帮助。

#6


6  

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 1中引用一个不属于同一类的类型的灵感):

 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");

#7


5  

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等其他事件,我也有类似的问题。如果我不想为每个控件类型派生新的类型,我就不能引发这些事件。

#8


3  

It seems that the code from the accepted answer by Wiebe Cnossen could be simplified to this one liner:

看来Wiebe所给出的答案的代码可以简化为这句话:

((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source))
    .DynamicInvoke(source, eventArgs);

#1


34  

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

#2


13  

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

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

Does the ButtonEdit control expose an OnClick method which you could call?

ButtonEdit控件公开一个可以调用的OnClick方法吗?

Footnote: Actually, events can have "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.

脚注:实际上,事件可以有“提升”成员,因此eventinfo . getraiethod。然而,c#从来没有对它进行过填充,我也不认为它在框架中是通用的。

#3


12  

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.

要通过反射完成它,只需找到private delegate字段,获取它,然后调用它。

#4


8  

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

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

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. :(

附注:代码的一些部分是从别人那里借来的。我真惭愧,我从哪儿弄到的都忘了。:(

#5


6  

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.

谢谢大家的帮助。

#6


6  

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 1中引用一个不属于同一类的类型的灵感):

 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");

#7


5  

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等其他事件,我也有类似的问题。如果我不想为每个控件类型派生新的类型,我就不能引发这些事件。

#8


3  

It seems that the code from the accepted answer by Wiebe Cnossen could be simplified to this one liner:

看来Wiebe所给出的答案的代码可以简化为这句话:

((Delegate)source.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(source))
    .DynamicInvoke(source, eventArgs);