代码之家  ›  专栏  ›  技术社区  ›  AnthonyWJones

在C中使用Lambda的单次事件#

  •  43
  • AnthonyWJones  · 技术社区  · 15 年前

    我发现自己在做这种事经常:-

     EventHandler eh = null;  //can't assign lambda directly since it uses eh
     eh = (s, args) =>
     {
         //small snippet of code here
    
         ((SomeType)s).SomeEvent -= eh;
     }
     variableOfSomeType.SomeEvent += eh;
    

    基本上我只想附加一个事件处理程序来监听事件中的一个镜头,之后我就不想再附加了。通常,“代码狙击手”只是一行。

    EventHandler 很可能是 EventHandler<T> .

    我能整理一下代码中重复的部分,把代码片段放在Lambda中吗?

    8 回复  |  直到 15 年前
        1
  •  9
  •   Jordão    6 年前

    可以使用反射:

    public static class Listener {
    
      public static void ListenOnce(this object eventSource, string eventName, EventHandler handler) {
        var eventInfo = eventSource.GetType().GetEvent(eventName);
        EventHandler internalHandler = null;
        internalHandler = (src, args) => {
          eventInfo.RemoveEventHandler(eventSource, internalHandler);
          handler(src, args);
        };
        eventInfo.AddEventHandler(eventSource, internalHandler);
      }
    
      public static void ListenOnce<TEventArgs>(this object eventSource, string eventName, EventHandler<TEventArgs> handler) where TEventArgs : EventArgs {
        var eventInfo = eventSource.GetType().GetEvent(eventName);
        EventHandler<TEventArgs> internalHandler = null;
        internalHandler = (src, args) => {
          eventInfo.RemoveEventHandler(eventSource, internalHandler);
          handler(src, args);
        };
        eventInfo.AddEventHandler(eventSource, internalHandler);
      }
    
    }
    

    variableOfSomeType.ListenOnce("SomeEvent", 
      (s, args) => Console.WriteLine("I should print only once!"));
    
    variableOfSomeType.ListenOnce<InterestingEventArgs>("SomeOtherEvent", 
      (s, args) => Console.WriteLine("I should print only once!"));
    
        2
  •  8
  •   dtb    15 年前

    OneShotHandlerQueue<EventArgs> queue = new OneShotHandlerQueue<EventArgs>();
    
    Test test = new Test();
    
    // attach permanent event handler
    test.Done += queue.Handle;
    
    // add a "one shot" event handler
    queue.Add((sender, e) => Console.WriteLine(e));
    test.Start();
    
    // add another "one shot" event handler
    queue.Add((sender, e) => Console.WriteLine(e));
    test.Start();
    

    代码:

    class OneShotHandlerQueue<TEventArgs> where TEventArgs : EventArgs {
        private ConcurrentQueue<EventHandler<TEventArgs>> queue;
        public OneShotHandlerQueue() {
            this.queue = new ConcurrentQueue<EventHandler<TEventArgs>>();
        }
        public void Handle(object sender, TEventArgs e) {
            EventHandler<TEventArgs> handler;
            if (this.queue.TryDequeue(out handler) && (handler != null))
                handler(sender, e);
        }
        public void Add(EventHandler<TEventArgs> handler) {
            this.queue.Enqueue(handler);
        }
    }
    

    class Test {
        public event EventHandler Done;
        public void Start() {
            this.OnDone(new EventArgs());
        }
        protected virtual void OnDone(EventArgs e) {
            EventHandler handler = this.Done;
            if (handler != null)
                handler(this, e);
        }
    }
    
        3
  •  7
  •   Reed Copsey    15 年前

    如果你能用 Reactive Extensions for .NET ,你可以简化这个。

    Observable from an event .Take(1) ,来完成您的小代码片段。这将整个过程转化为几行代码。


    编辑:为了演示,我制作了一个完整的示例程序(我将粘贴在下面)。

    我将可观察的创建和订阅移动到一个方法中( HandleOneShot ). 这样,您就可以用一个方法调用来执行您正在尝试的操作。为了演示,我创建了一个具有两个实现INotifyPropertyChanged的属性的类,并正在侦听 第一 属性更改事件,发生时写入控制台。

    这将获取您的代码,并将其更改为:

    HandleOneShot<SomeEventArgs>(variableOfSomeType, "SomeEvent",  e => { 
                        // Small snippet of code here
                    }); 
    

    运行时,此代码将打印:

    Setup...
    Setting first property...
     **** Prop2 Changed! /new val
    Setting second property...
    Setting first property again.
    Press ENTER to continue...
    

    你只有一次,一次触发你的事件。

    namespace ConsoleApplication1
    {
        using System;
        using System.ComponentModel;
        using System.Linq;
    
        class Test : INotifyPropertyChanged
        {
            private string prop2;
            private string prop;
            public string Prop
            {
                get {
                    return prop;
                }
                set
                {
                    if (prop != value)
                    {
                        prop = value;
                        if (PropertyChanged!=null)
                            PropertyChanged(this, new PropertyChangedEventArgs("Prop"));
                    }
                }
            }
    
            public string Prop2
            {
                get
                {
                    return prop2;
                }
                set
                {
                    if (prop2 != value)
                    {
                        prop2 = value;
                        if (PropertyChanged != null)
                            PropertyChanged(this, new PropertyChangedEventArgs("Prop2"));
                    }
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
        }
    
    
        class Program
        {
            static void HandleOneShot<TEventArgs>(object target, string eventName, Action<TEventArgs> action)  where TEventArgs : EventArgs
            {
                var obsEvent = Observable.FromEvent<TEventArgs>(target, eventName).Take(1);
                obsEvent.Subscribe(a => action(a.EventArgs));
            }
    
            static void Main(string[] args)
            {
                Test test = new Test();
    
                Console.WriteLine("Setup...");
                HandleOneShot<PropertyChangedEventArgs>(
                    test, 
                    "PropertyChanged", 
                    e =>
                        {
                            Console.WriteLine(" **** {0} Changed! {1}/{2}!", e.PropertyName, test.Prop, test.Prop2);
                        });
    
                Console.WriteLine("Setting first property...");
                test.Prop2 = "new value";
                Console.WriteLine("Setting second property...");
                test.Prop = "second value";
                Console.WriteLine("Setting first property again...");
                test.Prop2 = "other value";
    
                Console.WriteLine("Press ENTER to continue...");
                Console.ReadLine();
            }
        }
    }
    
        4
  •  2
  •   Community vonPryz    7 年前

    遇到其他用户 a very similar problem ,我相信这个线程中的解决方案适用于这里。

    特别是,你所拥有的 Queue{EventHandler} ,即在调用事件时将其出列。

        5
  •  1
  •   Jonathan Allen    15 年前

    它有用吗?如果是这样,那我就说去争取吧。一个看起来很优雅的一次性活动。

    • 如果s被垃圾收集,那么事件处理程序也会被垃圾收集。
    • 分离代码就在附加代码的旁边,这样可以很容易地看到您正在做什么。

        6
  •  1
  •   Wayne Bloss    14 年前

    就我个人而言,我只是为任何类型的事件创建了一个专门的扩展方法。

    下面是我现在正在使用的东西的基本版本:

    namespace MyLibrary
    {
        public static class FrameworkElementExtensions
        {
            public static void HandleWhenLoaded(this FrameworkElement el, RoutedEventHandler handler)
            {
                RoutedEventHandler wrapperHandler = null;
                wrapperHandler = delegate
                {
                    el.Loaded -= wrapperHandler;
    
                    handler(el, null);
                };
                el.Loaded += wrapperHandler;
            }
        }
    }
    

    我之所以认为这是最好的解决方案,是因为您通常不需要只处理一次事件。您还经常需要检查事件是否已经通过。。。例如,下面是上述扩展方法的另一个版本,它使用附加属性检查元素是否已加载,在这种情况下,它只需立即调用给定的处理程序:

    namespace MyLibraryOrApplication
    {
        public static class FrameworkElementExtensions
        {
            public static void HandleWhenLoaded(this FrameworkElement el, RoutedEventHandler handler)
            {
                if ((bool)el.GetValue(View.IsLoadedProperty))
                {
                    // el already loaded, call the handler now.
                    handler(el, null);
                    return;
                }
                // el not loaded yet. Attach a wrapper handler that can be removed upon execution.
                RoutedEventHandler wrapperHandler = null;
                wrapperHandler = delegate
                {
                    el.Loaded -= wrapperHandler;
                    el.SetValue(View.IsLoadedProperty, true);
    
                    handler(el, null);
                };
                el.Loaded += wrapperHandler;
            }
        }
    }
    
        7
  •  0
  •   Albino Cordeiro    12 年前

    通常,当我需要像您描述的那样一次性执行事件处理程序时,我真正需要的是:

    await variableOfSomeSort.SomeMethodAsync();
    //small snippet of code here
    
        8
  •  0
  •   belayon    7 年前

    有点像。。。

        private void OnCheckedIn(object sender, Session e)
        {
            EventHandler<Session> nextInLine = null; 
            lock (_syncLock)
            {
                if (SessionCheckedIn != null)
                {
                    nextInLine = (EventHandler<Session>)SessionCheckedIn.GetInvocationList()[0];
                    SessionCheckedIn -= nextInLine;
                }
            }
    
            if ( nextInLine != null )
            {
                nextInLine(this, e);
            }
        }