Как вызвать событие с помощью отражения в .NET / C#?

У меня есть сторонний редактор, который в основном состоит из текстового поля и кнопки (элемент управления DevExpress ButtonEdit). Я хочу, чтобы конкретное нажатие клавиши (Alt + Down) имитировало нажатие кнопки. Чтобы избежать повторения этого снова и снова, я хочу создать общий обработчик событий KeyUp, который будет вызывать событие ButtonClick. К сожалению, похоже, что в элементе управления нет метода, вызывающего событие ButtonClick, поэтому ...

Как вызвать событие из внешней функции через отражение?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
31
0
23 114
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Из Создание события через отражение, хотя я думаю, что ответ в 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");

Этот пример мне помог. Мне нужно было вызвать событие CLR в объекте приложения Windows в моем приложении WPF, и мне нужно было ссылаться на него через отражение. Поэтому вместо этого в методе FireEvent я использовал Application.Current. Отличный совет, при необходимости измените флаги привязки метода FireEvent.

Tore Aurstad 26.06.2018 19:03

Обычно вы не можете вызывать события других классов. События действительно хранятся как частное поле делегата плюс два метода доступа (add_event и remove_event).

Чтобы сделать это через отражение, вам просто нужно найти поле частного делегата, получить его, а затем вызвать его.

В общем, нельзя. Думайте о событиях как о парах методов AddHandler / RemoveHandler (поскольку это в основном то, чем они являются). Как они будут реализованы, зависит от класса. Большинство элементов управления WinForms используют EventHandlerList в качестве своей реализации, но ваш код будет очень хрупким, если он начнет извлекать закрытые поля и ключи.

Предоставляет ли элемент управления ButtonEdit метод OnClick, который вы могли бы вызвать?

Сноска: На самом деле, события может имеют элементы "повышения", следовательно, EventInfo.GetRaiseMethod. Однако это никогда не заполняется C#, и я также не верю, что это входит в структуру в целом.

Оказывается, я мог это сделать и не осознавал этого:

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

Но если бы я не мог, мне пришлось бы углубиться в исходный код и найти метод, который вызывает событие.

Всем спасибо за помощь.

Если вы знаете, что это кнопка, вы можете вызвать ее метод PerformClick(). У меня аналогичная проблема для других событий, таких как OnEnter, OnExit. Я не могу инициировать эти события, если не хочу создавать новый тип для каждого типа элемента управления.

«Выполнить щелчок»? Ты, должно быть, шутишь, это фантастика!

Autodidact 01.06.2010 09:24
Ответ принят как подходящий

Вот демонстрация использования дженериков (проверка ошибок опущена):

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, но ничего с ней не делаете. Это только у меня или можно первую строчку убрать?

Nick 12.10.2009 00:31

получать eventInfo не нужно. это бесполезно. в остальном хороший образец!

henon 21.12.2011 13:08

Я не считаю необходимым вызывать GetInvocationList (). Достаточно просто вызвать eventDelegate.

HappyNomad 18.07.2013 06:25

eventDelegate.DynamicInvoke (args) отлично работает, как заявил @HappyNomad.

kevindaub 23.08.2015 20:00

вы не используете eventInfo

Christo S. Christov 27.08.2015 15:40

Я написал расширение для классов, которое реализует 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;
        }
    }
}

Я удалил некоторые части исходного кода, поэтому расширение должно работать как есть, без ссылок на другие части моей библиотеки. Но на самом деле это не проверено.

P.S. Некоторые части кода были заимствованы у кого-то другого. Позор мне, что я забыл, откуда взял. :(

Большое спасибо. Фактически, ваш ответ здесь единственный, который выполняет исследование дерева наследования и правильную проверку нуля.

firegurafiku 06.11.2014 19:23

Почему вы кешируете аргументы событий? Требуемый багаж, словарь и замок, перевешивают любую потенциальную выгоду в производительности, imho. Я что-то упустил (общее состояние и т. д.)?

skataben 30.07.2015 17:53

Вы должны проверить Решение 1 здесь: stackoverflow.com/a/54789191/7108481

Jogge 20.02.2019 18:07

Кажется, что код из принятый ответ от Wiebe Cnossen можно упростить до следующего:

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

Другие вопросы по теме