【问题标题】:One-time event fire that is subscribed to another event订阅另一个事件的一次性事件触发
【发布时间】:2012-12-12 04:30:05
【问题描述】:
public event EventHandler ConstructDesign;
public DataGridView dataGrid = new DataGridView();
public FooClass(Action action) {
    ConstructDesign+=action;
    dataGrid.DataBindingComplete+=ConstructDesign;
}

public void Launch() {
    ConstructDesign(null, new EventArgs());
}

//IN A COMPLETELY DIFFERENT CLASS:
public void Main(string[] args) {
    var launcher = new FooClass(Fire);
    launcher.Launch();
}

public void Fire(object sender, EventHandlerArgs args...) {
    Console.WriteLine("Fired");
    //and after the first fire, action will be removed from the `ConstructDesign`.
}

所以基本上我在这里想要实现的是如何执行以下操作: 一个Action,通过代码手动添加到ConstructDesign,在触发时,它会从事件处理程序ConstructDesign中删除自己。有什么想法吗?

【问题讨论】:

    标签: c# events


    【解决方案1】:

    我还没有找到在第一次使用后取消订阅该事件的好方法。 (您当然可以使用大量反射的方法,但我怀疑如果重构更改了事件的名称,编译器会抱怨)。

    这是一个只使用委托的方法,所以编译器仍然可以很好地为您服务。它可能不像您需要的那么轻巧,但由于我接受了自己的启迪挑战,我想我会分享它。

    MyEvent += SingleUseEventHandler<AssemblyLoadEventArgs, AssemblyLoadEventHandler>
       .Create(This_MyEventOccurred);
    

    这里定义了魔法:

    public class SingleUseEventHandler<TArgs,THandler>
      where TArgs : EventArgs
    {
      public static THandler Create(EventHandler<TArgs> handler)
      {
         var helper = new SingleUseEventHandler<TArgs, THandler>(handler);
         EventHandler<TArgs> h = helper.InvokeIfFirstTime;
         return (THandler)(object)Delegate.CreateDelegate(typeof(THandler), h.Target, h.Method);
      }
    
      public void InvokeIfFirstTime(object sender, TArgs args)
      {
         if (!raised)
         {
            raised = true;
            handler(sender, args);
         }
      }
    
      public SingleUseEventHandler(EventHandler<TArgs> handler)
      {
         this.handler = handler;
      }
    
      bool raised;
      readonly EventHandler<TArgs> handler;
    }
    

    当然,C# 不会推断委托类型,因此您必须明确指定它。

    如果事件的定义是EventHandler,你可以用这个来代替:

    MyEvent += SingleUseEventHandler<SomeEventArgs>.Create(SomeHandlerMethod);
    
    
    public static class SingleUseEventHandler<TArgs>
      where TArgs : EventArgs
    {
      public static EventHandler<TArgs> Create(EventHandler<TArgs> handler)
      {
         var helper = new SingleUseEventHandler<TArgs, EventHandler<TArgs>>(handler);
         return helper.InvokeIfFirstTime;
      }
    }
    

    这是一个示例程序:

    class Program
    {
      static event AssemblyLoadEventHandler MyEvent;
      static int callCount;
      static void Main(string[] args)
      {
         MyEvent += SingleUseEventHandler<AssemblyLoadEventArgs, AssemblyLoadEventHandler>
            .Create(Load);
    
         foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies())
         {
            Console.WriteLine("Raising event for " + assembly.GetName().Name);
            MyEvent(null, new AssemblyLoadEventArgs(assembly));
         }
      }
    
      static void Load(object sender, AssemblyLoadEventArgs eventArgs)
      {
         Console.WriteLine(++callCount);
      }
    }
    

    【讨论】:

      【解决方案2】:

      我不相信您可以阻止事件触发,但您可以阻止事件中的代码执行。真的很简单,在类级别添加一个static bool,初始化为true,第一次执行后设置为false。将代码封装在if (firstExecution) {//actions I only want executed the first time the event fires}中的事件处理程序中

      【讨论】:

      • 我已经考虑过这个选项,但我相信会有更好的解决方案。谢谢。
      • 另外,当该特定方法 Fire 在其他事件中启动时,这会产生更大的问题,所以我不能使用您的建议,除非我会检查发件人对象以确定来自它是在哪里启动的。
      【解决方案3】:

      我不明白为什么你的课堂上有活动,因为你没有在任何地方订阅它们。改为调用传递的操作:

      Action _action;
      
      public FooClass(Action action) 
      {
          _action = action;
      }
      
      public void Launch() 
      {
          if (_action == null)
              return;
      
           _action();
           _action = null;        
      }
      

      【讨论】:

      • 事件 'ConstructDesign' 只能出现在 += 或 -= 的左侧(除非在 'FooClass' 类中使用。我认为这不会有什么不同,我在第一篇文章中编辑了代码。
      • @NucS 事件仅出现在-= 运算符的左侧。这里有什么问题?
      • ConstructDesign 是需要从otherHandler 中删除的那个。它位于FooClass 之外,我实际上可能解决了它,如果它有效,我会发布答案。
      • 我已经解决了这个问题。非常感谢您的努力。
      【解决方案4】:

      一个事件只有在它有订阅者的情况下才会触发,所以你不能通过-= 删除委托,还是我误解了你想要做什么?

      【讨论】:

      • 我相信 OP 想要一种功能性的方式来做到这一点。添加一次性处理程序的一些符号。
      • @agent-j,这正是我要找的。​​span>
      【解决方案5】:
              public class FooEvents {
                  public event EventHandler ConstructDesign;
                  public DataGridView dataGrid = new DataGridView();
                  public FooEvents(Action action) {
                      ConstructDesign+=action;
                      dataGrid.DataBindingComplete+=ConstructDesign;
                      dataGrid.DataBindingComplete+=RemoveSubscribtion;
                  }
      
                  public void Launch() {
                      ConstructDesign(this, new EventArgs()); //passes FooEvent and fires.
                  }
      
                  private void RemoveSubscribtion(object sender, EventArgs args) {
                       dataGrid.DataBindingComplete-=ConstructDesign;
                       dataGrid.DataBindingComplete-=RemoveSubscribtion;
                  }
      
             public Main {
                  public void Main(string[] args) {
                      var launcher = new FooClass(Fire);
                      launcher.Launch();
                  }
      
                  public void Fire(object sender, EventHandlerArgs args) {
                      Console.WriteLine("Fired");
                  }
             }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-03
        • 1970-01-01
        • 1970-01-01
        • 2016-04-22
        • 1970-01-01
        • 2022-01-08
        相关资源
        最近更新 更多