【问题标题】:How to manually invoke an event?如何手动调用事件?
【发布时间】:2012-01-04 22:02:09
【问题描述】:

我在 C# 中有以下行:

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

如何在不指定 _somefunction 的情况下调用订阅 _timer.ElapsedTick 的所有方法?沿着这条伪线的某个地方

invoke(_timer.ElapsedTick);

【问题讨论】:

标签: c# events


【解决方案1】:

您不能调用属于其他类型的事件。事件只能从声明它的类内部调用。

【讨论】:

  • @MatthewPatrickCashatt 是的,但是您正在使用反射。这可以用来绕过 CLR 和 C# 中的大量限制。一般来说,我不认为反射是“我该怎么做 X”的有效答案。它肯定有效
  • @MatthewPatrickCashatt in general 在回答“你如何在 C# 中做到这一点”问题时,我将我的答案限制在没有反射/pinvoke/COM 的语言可以做什么互操作。这些通常是代码库中的禁忌,因此通常不是正确。一旦你引入了反射,就有很多事情是可能的,而这些事情是用 C# 根本无法完成的,而且通常是有充分理由的。例如,允许您为我调用我的事件是危险的。它可以很容易地破坏我的消费者所依赖的微妙保证(例如图书结束事件)。
  • @MatthewPatrickCashatt 这绝不是一个硬性规定,也不是普遍同意的。这是我回答问题的方法。如果您查看我的历史,我相信您会找到很多我决定提出反思答案的地方。我通常会警告我的回答“在香草 C# 中不可能,但如果你想弄脏你的手”......
  • @JesonMartajaya 这并不总是很危险,但经常是。考虑我的类型有StartEnd 事件的场景。我提供的保证是我会按顺序饲养它们,Start 将始终伴随着End。因为我控制事件,所以我可以做出这个保证,另一个班级可以依赖于此。如果你突然可以控制我的事件,那么没有什么能阻止你在StartStart 之前无休止地提高End。此外,您提出事件可能会绕过我的类型,设置我处于Start / End 进程中间的标志。都非常糟糕
  • @MatthewPatrickCashatt 你说得对,当它是“它确实有效但你可能不应该这样做”时,我应该更加小心地说它“不起作用”。
【解决方案2】:

可以使用传统的 C# 来完成吗?否 (as previously stated)。但是使用反射是可能的。以下是基于this MSDN forum thread 的答案的一些经过测试的代码:

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

【讨论】:

  • FireEvent 正是我正在寻找的,几乎!它目前在 GetField 上给了我一个 FieldInfo=null。我错过了 BindingFlag 吗?目标事件是 System.Timer.Timer.ElapsedTick 。 somefunction 方法目前是私有的,但我可以将它们更改为内部或公共。
  • @JesonMartajaya - 你是说System.Timers.Timer.Elapsed吗?如果事件是私人的,这将不起作用。不过,事件处理程序可以是私有的。
  • @JesonMartajaya - 对于System.Timers.Timer 类,您需要像这样调用FireEventFireEvent(fromMe, "onIntervalElapsed", this, null);
  • e 参数必须为空,因为ElapsedEventArgs 没有公共构造函数,而ElapsedEventArgs.Empty 返回一个EventArgs。您也可以使用反射来实例化它,但我必须使用它才能确定。
  • 我将System.dll 带入了 ILSpy 并对事件进行了逆向工程。 Reflector、ILSpy 和 JustDecompile 等工具是 .NET 开发人员工具箱的必备工具。
【解决方案3】:

我最终在这里搜索如何简单地启动事件,它没有任何自定义参数或任何东西。我知道 OP 问题是不同的!但我想分享,我希望它可以帮助别人。

简化版:

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

简化版:

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

【讨论】:

    【解决方案4】:

    您可以创建一个运行它们的函数:

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

    【讨论】:

    • 绝对不是我想要的。除了查看 _timer.ElapsedTick 之外,我没有参考 somefunction 方法。
    【解决方案5】:

    这是一种变通方法,您不能循环遍历 Elapsed 事件。但是要调用它们,让计时器来完成工作。此代码为 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)
    

    类似的事情会触发事件,但您的原始间隔将重新开始。如果您想保留原始刻度模式,您可能需要在其中进行一些数学运算。

    【讨论】:

      【解决方案6】:

      我认为您可能需要使用 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;
      

      祝你好运!

      【讨论】:

      • 为了更清楚,我希望手动引发 _timer.ElapsedTick 事件。从您提出的解决方案中,您认为我可以调用 GetMethod("_timer.ElapsedTick") 吗?
      • 这很接近但并不完全。您的代码在这里直接运行一个方法。您需要首先获取表示类型上事件的字段为MulticastDelegate,然后获取其调用列表,然后您可以从那里单独动态调用每个方法。 this MSDN 问题的答案显示了如何做到这一点。
      • 您可以使用“GetMethod”调用任何您喜欢的方法,但只需看看@JaredPar 的cmets,因为他有一些保留意见。我一直在成功使用 GetMethod,但是当他警告它时,很难忽视一个拥有 178k 声誉的人。祝你好运!
      • @MatthewPatrickCashatt - 是的,您是对的,您可以使用您提供的代码直接调用任何 方法,但这不是问题。
      • @M.Babcock- 我看到你很擅长用我的回答(有效)来扮演魔鬼的拥护者。也许您可以提供自己的答案,而不是指出我的答案不正确/没有回答问题?我们是软件开发人员,而不是争论语义的律师。让我们帮助这个家伙,好吗?
      【解决方案7】:

      基于上述M.Babcock's answer,对反射方法进行了一些细微的更新。

      GetField 替换为GetTypeInfoGetDeclaredField,将dlg.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);
              }
          }
      

      【讨论】:

        【解决方案8】:

        根据ElapsedTick 事件的签名,您可以通过在声明它的类内部调用它来触发该事件。例如:

        ElapsedTick(this, new EventArgs());
        

        【讨论】:

        • EventArgs.Empty 很擅长避免不必要的分配。此外,您不能像那样从类外部调用事件。
        • 我没有提到它是从课堂内部以这种方式触发的。此外,使用EventArgs.Empty 也不错。
        • 我尝试了以下 _timer.ElapsedTick(this, ElapsedEventArgs.Empty);并且它给出了编译时错误:“System.Timers.Timer”不包含“ElapsedTick”的定义,并且找不到接受“System.Timers.Timer”类型的第一个参数的扩展方法“ElapsedTick”(是您缺少 using 指令或程序集引用?)
        • 我不明白这是对这个问题的回答(尽管我没有否决这个答案)。 OP 在类中编写代码(在这种情况下,是类Timer)。那么他如何利用这个建议呢? [回答我自己的问题:也许通过继承Timer,并添加一个包含这行代码的公共方法。这个答案需要说明如何做到这一点。]
        【解决方案9】:

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

        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 类外部的委托中调用它们。

        【讨论】:

        • 在许多情况下,跨类屏障调用事件是可能的。诀窍是跨程序集进行。
        猜你喜欢
        • 2017-01-07
        • 1970-01-01
        • 2011-04-19
        • 1970-01-01
        • 1970-01-01
        • 2010-10-19
        • 1970-01-01
        • 1970-01-01
        • 2010-11-10
        相关资源
        最近更新 更多