実行できるサンプルコードを記載します。
「イベントを await で受け取る」に記載のコードを使うと、イベントスパゲティを簡単にほどき、次のように for 文で扱うことができるようになります。
なお、初心者の学習向けのコードではありません。
誰か他の人が回答してくれるでしょうが、フォームにプロパティやフィールドを作って状態管理するのが定石です。
csharp
1using System;
2using System.Threading.Tasks;
3using System.Windows.Forms;
4using Zuishin.WaitEvent;
5
6namespace WindowsFormsApp1
7{
8 public partial class Form1 : Form
9 {
10 public Form1()
11 {
12 InitializeComponent();
13
14 // ToggleButtonTextWithClick に button1 を登録
15 _ = ToggleButtonTextWithClick(button1);
16 }
17
18 // 引数で指定されたボタンが押されるたびにテキストを変える
19 private async Task ToggleButtonTextWithClick(Button button)
20 {
21 var buttonTexts = new[] { "a", "b", "c" };
22
23 for (int i = 0; ; i = (i + 1) % buttonTexts.Length)
24 {
25 button.Text = buttonTexts[i];
26 await button.WaitEvent<EventArgs>(nameof(button.Click));
27 }
28 }
29 }
30}
これを動かすためには、リンク先で記載されているコードが必要です。
プロジェクトに新しいクラスを追加して次のように書き換えてください。
csharp
1using System;
2using System.Collections.Concurrent;
3using System.Linq.Expressions;
4using System.Reflection;
5using System.Threading;
6using System.Threading.Tasks;
7
8namespace Zuishin.WaitEvent
9{
10 static class WaitEventExtensions
11 {
12 private static readonly ConcurrentDictionary<Type, Delegate> handlerFactoryCache = new ConcurrentDictionary<Type, Delegate>();
13
14 public static Task<(object Sender, TEventArgs EventArgs)> WaitEvent<TEventArgs>(
15 this object sender,
16 string eventName,
17 CancellationToken? cancellationToken = null)
18 {
19 if (sender is null) throw new ArgumentNullException(nameof(sender));
20 if (eventName is null) throw new ArgumentNullException(nameof(eventName));
21
22 var targetEvent = sender
23 .GetType()
24 .GetEvent(eventName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
25 ?? throw new ArgumentException($"{nameof(sender)} has no event named {eventName}.");
26
27 var completionSource = new TaskCompletionSource<(object, TEventArgs)>();
28 cancellationToken?.Register(() => completionSource.SetCanceled());
29
30 var handler = GetHandler(targetEvent.EventHandlerType, completionSource);
31 targetEvent.AddEventHandler(sender, handler);
32
33 return completionSource.Task.ContinueWith(task =>
34 {
35 targetEvent.RemoveEventHandler(sender, handler);
36 return task.Result;
37 });
38 }
39
40 private static Delegate GetHandler<TEventArgs>(Type eventHandlerType, TaskCompletionSource<(object, TEventArgs)> completionSource)
41 {
42 var factory = handlerFactoryCache.GetOrAdd(eventHandlerType, _ =>
43 {
44 var taskCompletionSourceType = typeof(TaskCompletionSource<(object, TEventArgs)>);
45 var taskCompletionSourceExpression = Expression.Parameter(taskCompletionSourceType);
46 var setResult = taskCompletionSourceType.GetMethod(nameof(TaskCompletionSource<(object, TEventArgs)>.SetResult));
47 var sExpression = Expression.Parameter(typeof(object));
48 var eExpression = Expression.Parameter(typeof(TEventArgs));
49 var tupleConstructor = typeof(ValueTuple<object, TEventArgs>).GetConstructor(new[] { typeof(object), typeof(TEventArgs) });
50 var expression = Expression.Lambda<Func<TaskCompletionSource<(object, TEventArgs)>, Delegate>>(
51 Expression.Lambda(
52 eventHandlerType,
53 Expression.Call(
54 taskCompletionSourceExpression,
55 setResult,
56 Expression.New(tupleConstructor, sExpression, eExpression)),
57 sExpression,
58 eExpression),
59 taskCompletionSourceExpression);
60 return expression.Compile();
61 });
62 return ((Func<TaskCompletionSource<(object, TEventArgs)>, Delegate>)factory)(completionSource);
63 }
64 }
65}