如何手动调用事件

本文关键字:事件 调用 何手动 | 更新日期: 2023-09-27 18:19:39

我在 C# 中有以下行:

_timer.ElapsedTick += _somefunction1;
_timer.ElapsedTick += _somefunction2;
_timer.ElapsedTick += _somefunction3;

如何调用订阅_timer的所有方法。已过滴答而不指定_somefunction?沿着这条伪线的某个地方

invoke(_timer.ElapsedTick);

如何手动调用事件

不能调用由其他类型拥有的事件。 事件只能从声明它的类的内部调用。

可以使用传统的 C# 来完成吗?否(如前所述(。但是使用反射是可能的。下面是一些基于此 MSDN 论坛主题的答案的测试代码:

class InvokeFromMe
{
    public event EventHandler RaiseMe;
}
class Program
{
    static void Main(string[] args)
    {
        var fromMe = new InvokeFromMe();
        fromMe.RaiseMe += fromMe_RaiseMe;
        fromMe.RaiseMe += fromMe_RaiseMe1;
        fromMe.RaiseMe += fromMe_RaiseMe2;
        FireEvent(fromMe, "RaiseMe", null, EventArgs.Empty);
    }
    static void fromMe_RaiseMe(object sender, EventArgs e)
    {
        Console.WriteLine("Event Handler 0 Raised");
    }
    static void fromMe_RaiseMe1(object sender, EventArgs e)
    {
        Console.WriteLine("Event Handler 1 Raised");
    }
    static void fromMe_RaiseMe2(object sender, EventArgs e)
    {
        Console.WriteLine("Event Handler 2 Raised");
    }
    public static void FireEvent(object onMe, string invokeMe, params object[] eventParams)
    {
        MulticastDelegate eventDelagate =
              (MulticastDelegate)onMe.GetType().GetField(invokeMe,
               System.Reflection.BindingFlags.Instance |
               System.Reflection.BindingFlags.NonPublic).GetValue(onMe);
        Delegate[] delegates = eventDelagate.GetInvocationList();
        foreach (Delegate dlg in delegates)
        {
            dlg.Method.Invoke(dlg.Target, eventParams);
        }
    } 
}

更新

我不熟悉System.Timer.Timer类,所以我不确定与我提供的示例有什么不同。你也许可以尝试这样的事情:

public static void FirePublicEvent(object onMe, string invokeMe, params object[] eventParams)
{
    MulticastDelegate eventDelagate =
          (MulticastDelegate)onMe.GetType().GetField(invokeMe,
           System.Reflection.BindingFlags.Instance |
           System.Reflection.BindingFlags.Public).GetValue(onMe);
    Delegate[] delegates = eventDelagate.GetInvocationList();
    foreach (Delegate dlg in delegates)
    {
       dlg.Method.Invoke(dlg.Target, eventParams);
    }
} 

好吧,我在这里结束了关于如何简单地启动事件的内容,它没有任何自定义参数或任何东西。我知道OP问题是不同的!但我想分享,我希望它能帮助到某人。

非简化版本:

 var Handler = EventNameHere;
        if (Handler != null)
        {
            Handler(this, EventArgs.Empty);
        }

简化版本:

 EventNameHere?.Invoke(this, EventArgs.Empty);

您可以创建一个运行所有这些的函数:

public void RunAllFuncs()
{
   _somefunction1();
   _somefunction2();
   _somefunction3();
}

这是一个解决方法,您无法循环遍历已用事件。 但是要全部调用它们,请让计时器完成工作。 此代码用于 System.Timer,不确定您使用的是哪个计时器。

假设计时器已启用:

int interval = timer.Interval;
ElapsedEventHandler handler = null;
handler = (s,e) =>
{
    timer.Interval = interval; // put interval back to original value
    timer.Elapsed -= handler;
};
timer.Elapsed += handler;
timer.Interval = 1; // 1 millisecond, pretty much going to fire right now (as soon as you let it)

类似的事情将触发事件,但您的原始间隔将重新启动。 如果你想保持原始的即时报价模式,你可能必须在那里做一些数学运算。

我认为您可能需要使用InvokeMember:

public void GetMethod(string methodName){
            var args = new Object[] { [INSERT ARGS IF NECESSARY--SET TO NULL OTHERWISE] };
            try
            {
                var t = new [INSERT NAME OF CLASS THAT CONTAINS YOUR METHOD]();
                Type typeInfo = t.GetType();
                var result = typeInfo.InvokeMember(methodName, BindingFlags.InvokeMethod, null, t, args);
                return result;
            }
            catch (Exception ex)
            {
                return ex;
            }
}

然后这样称呼它:

_timer.ElapsedTick += GetMethod("[INSERT METHOD NAME AS STRING]");

请务必包括以下内容:

using System.Reflection;

祝你好运!

基于M.Babcock上面的回答,有一些轻微的反射方法更新。

GetField替换为GetTypeInfoGetDeclaredFielddlg.Method替换为dlg.GetMethodInfo

    using System.Reflection;
    ...
    public static void FireEvent(this object onMe, string invokeMe, params object[] eventParams)
    {
        TypeInfo typeInfo = onMe.GetType().GetTypeInfo();
        FieldInfo fieldInfo = typeInfo.GetDeclaredField(invokeMe);
        MulticastDelegate eventDelagate = (MulticastDelegate)fieldInfo.GetValue(onMe);
        Delegate[] delegates = eventDelagate.GetInvocationList();
        foreach (Delegate dlg in delegates)
        {
            dlg.GetMethodInfo().Invoke(dlg.Target, eventParams);
        }
    }

对于 .NET 6(也可以与其他版本一起使用(,以下泛型方法可能会有所帮助。与其他答案一样,它使用反射来调用事件处理程序:

private void InvokeEvent<T, TEvent>(T target, string eventName, TEvent @event)
{
   // some reflection magic to fire an event from outside the target class:
   if(target == null)
   {
      throw new ArgumentNullException(nameof(target));
   }
   Type targetType = target.GetType();
   FieldInfo? fi = targetType.GetField(eventName, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
   if(fi != null)
   {
      EventHandler<TEvent>? eventHandler = fi.GetValue(target) as EventHandler<TEvent>;
      if(eventHandler != null)
      {
         eventHandler.Invoke(this, @event);
      }
   }
}

然后可以像这样调用它:

const string eventName = nameof(Algorithm.Received);
DomainEvent @event = new DomainEvent(payload);
InvokeEvent(targetObject, eventName, @event);

笔记:

  • 如果将多个方法与+=添加到事件处理程序,则上面的代码也有效。
  • 如果要在产品中使用此代码,则可能需要考虑更多的错误处理,例如参数检查或在找不到特定事件处理程序时引发异常。
  • 对于 .NET 的每个主要版本,可能需要更改使用反射的代码。由于.NET 6是跨平台运行的Windows,Linux,MacOS等。我怀疑更改破坏上述代码的可能性降低了,尽管不是零。
  • 我们在自动测试套件中使用此代码。如果使用反射的代码确实发生了变化,我们只需要更改此方法。

基于我面前的答案,以下内容对我有用:

EventNameHere(null, EventArgs.Empty);

根据 ElapsedTick 事件的签名,可以通过从声明它的类内部像方法一样调用事件来触发事件。例如:

ElapsedTick(this, new EventArgs());

您只能从声明它的类内部调用事件。如果您编写了计时器类,则可以像这样调用它:

this.ElapsedTick(this, eventArgs);

在计时器类之外,您可以执行以下操作来获得类似的行为:

delegate void delegateSample(object sender, EventArgs e);
delegateSample d;
d += new delegateSample(_somefunction1);
d += new delegateSample(_somefunction2);
d(this, EventArgs.Empty);

delegateSample 实际上应该是 ElapsedTick 事件的类型,而不是声明另一个委托类型。我只是不知道那个代表的名字。只需确保将每个函数添加到 ElapsedTick 事件和您声明的委托变量,然后您就可以从 Timer 类之外的委托调用它们。