#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)
Вот еще несколько примеров