Оценка статических конструкторов модулей с отражением

#f# #system.reflection #static-constructor

#f# #system.reflection #статический конструктор

Вопрос:

У меня есть много модулей, которые при запуске программы должны добавлять определенные элементы в один словарь, найденный в модуле более высокого уровня. Однако, похоже, что выражения и константы внутри модуля упаковываются в статические конструкторы при компиляции в консольное приложение, поэтому они не оцениваются, если на них явно не ссылаются / когда программа считает, что они необходимы.

Здесь было несколько вопросов относительно инициализации модулей, и консенсус заключался в том, что принудительно выполнить это невозможно. Однако я не видел, чтобы кто-либо из них исследовал отражение в этом отношении. Я знаю, что в C # вы можете вызывать статический конструктор типа, поэтому я попытался сделать то же самое с модулями F #.

Мои попытки включали добавление пользовательского атрибута (MessageHandlerAttribute) к каждому модулю, содержащему такое выражение, которое я хочу вычислить при запуске программы, а затем выполнить это:

 let initAllMessageHandlerModules =
    Assembly.GetExecutingAssembly().GetTypes() 
    |> Array.choose (fun typ -> 
        typ.CustomAttributes 
        |> Seq.tryFind (fun attr -> attr.AttributeType = typeof<MessageHandlerAttribute>)
        |> Option.map (fun _ -> typ))
    |> Array.iter 
        (fun typ -> try typ.TypeInitializer.Invoke(null, null) |> ignore with | ex -> printfn "%A" ex)
  

Но это выдает мне следующую ошибку:
Исключение System.NullReferenceException: ссылка на объект не установлена на экземпляр объекта.

Я также попытался заменить конечную лямбда-функцию этим:

 (fun typ -> try System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typ.TypeHandle) |> ignore with | ex -> printfn "%A" ex)
  

Но, похоже, это ничего не дает. Возможно ли этого добиться?

Комментарии:

1. В качестве отступления — если вы уже используете отражение для их вызова, не могли бы вы поместить код инициализации в обычный статический метод вместо этого? (Это может быть проще вызвать через отражение …)

2. Да, вы правы, это то, что я сейчас делаю, чтобы заставить это работать. Код инициализации этих модулей просто помещается в функции (к которым я прикрепляю пользовательский атрибут вместо модуля), которые принимают только единичный аргумент и вызываются посредством вызова динамического метода. Единственная проблема, с которой я сталкиваюсь, заключается в том, что это приведет к сбою в тех местах, где я забываю добавить ()-аргумент, чтобы заставить их функционировать в отличие от констант. Я просто надеялся, что этот подход инициализатора типа сработает, в духе «если он компилируется, это, вероятно, правильно».

3. Вы используете Seq.tryFind , и Seq s лениво вычисляются — может ли это быть проблемой?

4. Я так не думаю; типы модулей действительно успешно найдены этим конвейером. Однако, конечная лямбда-функция завершается ошибкой при инициализации их статических конструкторов (что, я даже не знаю, обязательно возможно).

Ответ №1:

Используйте тип.Получаем constructor вместо type.Инициализатор типов.

 (fun typ -> try typ.GetConstructor(BindingFlags.Instance ||| BindingFlags.Public, null, CallingConventions.Standard, Array.empty<Type>, Array.empty<ParameterModifier>).Invoke(null, null) |> ignore with | ex -> printfn "%A" ex)
  

Вот еще несколько примеров