[英]How do I raise an event via reflection in .NET/C#?
我有一個第三方編輯器,基本上由一個文本框和一個按鈕(DevExpress ButtonEdit 控件)組成。 我想讓一個特定的擊鍵( Alt + Down )模擬點擊按鈕。 為了避免一遍又一遍地寫這個,我想制作一個通用的 KeyUp 事件處理程序來引發 ButtonClick 事件。 不幸的是,控件中似乎沒有引發 ButtonClick 事件的方法,所以......
如何通過反射從外部函數引發事件?
這是一個使用泛型的演示(省略了錯誤檢查):
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();
}
}
一般來說,您不能。 將事件基本上看作是成對的AddHandler
/ RemoveHandler
方法(因為它們基本上就是它們)。 如何實現它們取決於班級。 大多數WinForms控件使用EventHandlerList
作為其實現,但是如果代碼開始獲取私有字段和鍵,則代碼將非常脆弱。
ButtonEdit
控件是否公開了可以調用的OnClick
方法?
腳注:實際上,事件可以具有“ raise”成員,因此為EventInfo.GetRaiseMethod
。 但是,C#永遠不會填充它,而且我也不認為它通常在框架中。
您通常無法引發其他課堂活動。 事件實際上存儲為私有委托字段,外加兩個訪問器(add_event和remove_event)。
要通過反射進行操作,您只需要找到私有委托字段,獲取它,然后調用它即可。
我編寫了一個類擴展,該類實現了INotifyPropertyChanged來注入RaisePropertyChange <T>方法,因此可以這樣使用它:
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;
}
}
}
我刪除了原始代碼的某些部分,因此該擴展應按原樣工作,而無需引用庫的其他部分。 但這還沒有經過實際測試。
PS:代碼的某些部分是從別人那里借來的。 真可惜,我忘了從哪里買的。 :(
從“通過反思引發事件”開始 ,盡管我認為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");
事實證明,我可以做到這一點,卻沒有意識到:
buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down);
但是,如果我做不到,就必須深入研究源代碼並找到引發事件的方法。
謝謝大家的幫助。
如果您知道該控件是一個按鈕,則可以調用其PerformClick()
方法。 對於其他事件,例如OnEnter
, OnExit
,我也有類似的問題。 如果我不想為每種控件類型派生新類型,則無法引發這些事件。
Wiebe Cnossen 接受的答案中的代碼似乎可以簡化為:
private void RaiseEventViaReflection(object source, string eventName)
{
((Delegate)source
.GetType()
.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(source))
.DynamicInvoke(source, EventArgs.Empty);
}
對一些現有答案和評論的進一步細化。
這也考慮到委托字段可以在繼承的類上定義。
public static void RaiseEvent<TEventArgs>(this object source, string eventName, TEventArgs eventArgs)
where TEventArgs : EventArgs
{
// Find the delegate and invoke it.
var delegateField = FindField(source.GetType(), eventName);
var eventDelegate = delegateField?.GetValue(source) as Delegate;
eventDelegate?.DynamicInvoke(source, eventArgs);
// This local function searches the class hierarchy for the delegate field.
FieldInfo FindField(Type type, string name)
{
while (true)
{
var field = type.GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
if (field != null)
{
return field;
}
var baseType = type.BaseType;
if (baseType == null)
{
return null;
}
type = baseType;
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.