Я надеялся ограничить серию постов тремя записями, но вдруг вспомнил, что совсем забыл ещё один интересный метод – eventof, возвращающий экземпляр System.Reflection.EventInfo по выражению доступа к событию. Вот только сначала надо разобраться, что есть «выражение доступа к событию» в языке F#. Дело в том, что событие в CLI – это всего лишь два метода (add_EventName и remove_EventName) и метаинформация, объединяющая их.

class Foo {
  public event EventHandler Completed;
}

Такое событие в C# вне класса Foo можно использовать только в выражениях добавления или удаления подписчика на событие:

var foo = new Foo();
foo.Completed += SomeMethodName;
foo.Completed -= SomeMethodName;

Однако F# поддерживает события первого класса (first-class events), что позволяет «материализовать» событие и пользоваться им как любым другим значением. Например, определим в F# новый тип с двумя событиями - CLI-совместимым событием Bar и обычным событием F# Baz:

type Foo() =
  let bar = Event<int>()
  let baz = Event<int>()

  [<CLIEvent>]
  member __.Bar = bar.Publish
  member __.Baz = baz.Publish

Теперь, например, можно сложить оба события в список и подписаться на них в цикле:

for e in [ foo.Bar; foo.Baz ] do
  e.AddHandler(fun _ _ -> printfn "!")

Вопрос в том, что собой представляют выражения foo.Bar и foo.Baz, а ответит на этот вопрос система цитирования F#:

let foo = Foo()

<@ foo.Bar @>
   Call (None,
      IEvent`2[...] CreateEvent[FSharpHandler`1,Int32](...),
      [Lambda (eventDelegate,
               Call (Some foo,
                     Void add_Bar(FSharpHandler`1[Int32]),
                     [eventDelegate])),
       Lambda (eventDelegate,
               Call (Some foo,
                     Void remove_Bar(FSharpHandler`1[Int32]),
                     [eventDelegate])),
       Lambda (callback,
               NewDelegate (FSharpHandler`1[Int32],
                            [ a1; a2 ],
                            Application (
                               Application (callback, a1), a2)))])

<@ foo.Baz @>
   PropertyGet (Some foo,
                IEvent`2[FSharpHandler`1[Int32],Int32] Baz, [])

Ага, свойства F# - это всего лишь обычные CLI-свойства, возвращающие объекты типа IEvent, а вот для обычных CLI-событий F# генерирует по месту обращения вызов скрытого метода CreateEvent из стандартной библиотеки F#. Данный метод возвращает объект типа IEvent, получая несколько анонимных функций, предназначенных для подписки и отписки от события. То есть F# позволяет единообразно работать с любыми событиями, как с первоклассными сущностями типа IEvent, при этом компилятор скрывает процесс обёртки пары add/remove-методов в IEvent.

Вернёмся к исходной задаче – получение экземпляра EventInfo. Так как EventInfo генерируется только для CLI-совместимых событий, то всё, что нам надо сделать – научиться доставать пару add/remove-методов из генерируемой F# обёртки, и по этой паре извлекать из типа экземпляр объекта EventInfo. Попробуем:

let eventof expr =
  match expr with
  | Call(None, createEvent, [
          Lambda(arg1, Call(_,    addHandler, [ Var var1 ]))
          Lambda(arg2, Call(_, removeHandler, [ Var var2 ]))
          Lambda(_, NewDelegate _)
        ])
    when createEvent.Name = "CreateEvent"
      &&    addHandler.Name.StartsWith("add_")
      && removeHandler.Name.StartsWith("remove_")
      && arg1 = var1
      && arg2 = var2 ->
         addHandler.DeclaringType.GetEvent(
             addHandler.Name.Remove(0, 4), // имя события
             BindingFlags.Public ||| BindingFlags.Instance |||
             BindingFlags.Static ||| BindingFlags.NonPublic)

  | _ -> failwith "Not a event expression"

То есть ищем вызов метода с именем CreateEvent, получающего три анонимных функции в качестве параметров, две из которых содержат внутри вызов методов с именами, начинающимися на add_ и remove_. Далее из имени метода подписки выделяется имя события, путём удаления префикса add_ в начале строки и производится поиск в типе, определяющим метод подписки, событие с данным именем. Проверяем:

eventof<@ foo.Bar @>

val it : EventInfo =
  FSharpHandler`1[System.Int32] Bar
    {Attributes = None;
     DeclaringType = Foo;
     EventHandlerType = FSharpHandler`1[System.Int32];
     IsMulticast = true;
     MemberType = Event;
     Name = "Bar";
     ...}