3

I'm making a project where there are separate source files/modules that add functions to a single Dictionary contained in a higher level file. However, I find that nothing in these source files evaluates on its own, even functions that take no arguments/code that isn't even inside a function.

As a result nothing is being added to the Dictionary. Is there a way to forcibly evaluate complete function calls in a module automatically? I'll give an example of what I'm trying to do:

Registry.fs:

let private functions = Dictionary<string, MessageHandler>()

let add type handler = 
    functions.Add(type, handler)

Handler1.fs:

Registry.add "type1" (fun m -> ....
)

Handler2.fs:

Registry.add "type2" (fun m -> ....
)
Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108
FPguy
  • 127
  • 6
  • 1
    Thanks for the code format edit :) – FPguy Feb 14 '19 at 21:50
  • Can you give more details on when is `Registry.add` called from `Handler1` and `Handler2`. Are the calls inside methods or something? – Ivaylo Slavov Feb 14 '19 at 21:51
  • The Registry.add calls are not inside anything. they were supposed to be standalone calls that register my message handlers automatically with a message type and the lambda function being the handler itself. – FPguy Feb 14 '19 at 21:55
  • How are you compiling the project that contains the files with the `Registry.add` calls? Is this compiled as an executable, library, or in some other way? – Tomas Petricek Feb 14 '19 at 22:09
  • It is compiled as a console app with visual studio. – FPguy Feb 14 '19 at 22:14

1 Answers1

3

I believe you need to see this relevant topic. Loose method calls would get compiled as method calls inside of a static constructor for the enclosing type/module, when the F# code gets compiled to IL. This would roughly be equivalent to the following C# code, just to see the picture:

static class Handler1 {
    static Handler1() {
        // this is the static constructor
        Registry.add "type1" .... 
    }
}

In .NET static constructors are not eagerly initialized1. This means, if you want to cause the .NET runtime to call the Handler1 static constructor, you need to access a static member of the type Handler1.

An example of using the type in a static context would be to

  1. Expose a sufficiently accessible static member/method:

    module Handler1 =
        [<Literal>]
        let Name = "Handler1"
    
  2. Access that static member from your code, such as the main method:

    [<EntryPoint>]
    let main args =
        printf Handler1.Name 
    

The above line will force the .NET runtime to load the Handler1 type's static context, which will result in invoking the static constructor if the type is encoutered by your code for the first time. If your code never encounters a given type's static context (any static member or method), then it will never be initialized -- the static constructors will never get called.

This behaviour is by design of the .NET framework (and that is regardless of the chosen language -- C#, F#, VB, others -- they all compile to similar IL). The point is to not allocate unnecessary resources by types that are never actually used.


1 Until .NET 4, static type context was initialized when the given type was first encountered by the executing code, regardless if the user code is interacting with instace or static members of that type. After .NET 4, this slightly changed -- the static context is initialized only when the user code interacts with static members of the type.

Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108
  • 1
    Thanks for the explanation! I guess this fact breaks my solution, because I have a ton of these handlers, so I simply can't load every handler explicitly from an entry point. Unfortunate since I liked the way this approach enforced the signature of my handlers compile time, but I guess I will have to fall back on reflection. – FPguy Feb 14 '19 at 22:32
  • @FPguy, I think there would be a way to avoid part of the reflection performance boilerplate. If you decorate your assembly with custom attributes, containing a static member like the `Name` literal, you could go away by: first loading the assembly via reflection, and second -- calling `myAssembly.GetCustomAttributes()` from your entry point. Not sure if this will work 100%, but I belive it should. The good think is you need not have to address every handler individually, just the assembly that contains it – Ivaylo Slavov Feb 14 '19 at 22:34
  • I'm not sure I understand exactly what you mean. I was thinking about attaching custom attributes to my message handler functions, and finding them based on that. Do you mean that, or do you mean using reflection somehow to invoke the Registry.add calls? – FPguy Feb 14 '19 at 23:08
  • @FPguy, similar but that would not work as I initially suspected. I thought of having something like `ActivateHandlerAttribute(handlerID : string)` and use it like `[]` but since attribute arguments must be compile-time constants, the compiler would inline the `Handler1.Name` at compile time. This would bypass the opportunity to access `Handler1` at runtime, which you need to do. Anyways, I was just thinking of an easier ways and that is not one of them... – Ivaylo Slavov Feb 15 '19 at 12:55