[ACCEPTED]-How do I raise an event via reflection in .NET/C#?-devexpress
Here's a demo using generics (error checks 1 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();
}
}
In general, you can't. Think of events as 11 basically pairs of AddHandler
/RemoveHandler
methods (as that's 10 basically what what they are). How they're 9 implemented is up to the class. Most WinForms 8 controls use EventHandlerList
as their implementation, but 7 your code will be very brittle if it starts 6 fetching private fields and keys.
Does the 5 ButtonEdit
control expose an OnClick
method which you could 4 call?
Footnote: Actually, events can have "raise" members, hence 3 EventInfo.GetRaiseMethod
. However, this is never populated by C# and 2 I don't believe it's in the framework in 1 general, either.
You can't normally raise another classes 5 events. Events are really stored as a private 4 delegate field, plus two accessors (add_event 3 and remove_event).
To do it via reflection, you 2 simply need to find the private delegate 1 field, get it, then invoke it.
I wrote an extension to classes, which implements 11 INotifyPropertyChanged to inject the RaisePropertyChange<T> method, so 10 I can use it like this:
this.RaisePropertyChanged(() => MyProperty);
without implementing 9 the method in any base class. For my usage 8 it was to slow, but maybe the source code 7 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 6 some parts of the original code, so the 5 extension should work as is, without references 4 to other parts of my library. But it's not 3 really tested.
P.S. Some parts of the code 2 was borrowed from someone else. Shame on 1 me, that I forgot from where I got it. :(
From Raising an event via reflection, although I think the answer in VB.NET, that 4 is, two posts ahead of this one will provide 3 you with the generic approach (for example, I'd 2 look to the VB.NET one for inspiration on 1 referencing a type not in the same class):
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");
As it turns out, I could do this and didn't 4 realize it:
buttonEdit1.Properties.Buttons[0].Shortcut = new DevExpress.Utils.KeyShortcut(Keys.Alt | Keys.Down);
But if I couldn't I would've 3 have to delve into the source code and find 2 the method that raises the event.
Thanks 1 for the help, all.
It seems that the code from the accepted answer by Wiebe 1 Cnossen could be simplified to this:
private void RaiseEventViaReflection(object source, string eventName)
{
((Delegate)source
.GetType()
.GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(source))
.DynamicInvoke(source, EventArgs.Empty);
}
If you know that the control is a button 4 you can call its PerformClick()
method. I have similar 3 problem for other events like OnEnter
, OnExit
. I can't 2 raise those events if I don't want to derive 1 a new type for each control type.
More Related questions
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.