Skip to content

[<CLIEvent>] on abstract type member generates accessor methods without specialname #5834

@stakx

Description

@stakx

F# does not mark CLI event accessor methods (add_X, remove_X) with the specialname IL flag if the event member is abstract.

This can be problematic for Reflection-based tools because they might not correctly recognize event accessor methods as such.

Someone stumbled on this a while ago when using the Moq mocking library against a F#-defined interface type. In order to keep up support for F#, Moq had to stop querying method.IsSpecialName, which it used to quickly check whether it was worth trying to find a property or event associated with method (a much more expensive operation). Abandoning that check for F# also meant reduced runtime performance for all other (C#) code.

I'd like to bring back this performance optimization in Moq. Therefore it would be great if F# could correctly mark all event accessors as such in metadata.

Repro steps

Compile the following code as a console application, run it, and observe its output.

open System;
open System.Reflection

type IAbstract1 =
    [<CLIEvent>]
    abstract member Event : IEvent<EventHandler, EventArgs>

type IAbstract2 =
    [<CLIEvent>]
    abstract member Event : IDelegateEvent<Action>

[<AbstractClass>]
type Abstract3() =
    [<CLIEvent>]
    abstract member Event : IDelegateEvent<Action>

type Concrete1() =
    let event = new Event<EventHandler, EventArgs>()
    [<CLIEvent>]
    member this.Event = event.Publish

type Concrete2() =
    [<CLIEvent>]
    member this.Event = { new IDelegateEvent<Action> with
                              member this.AddHandler _ = ()
                              member this.RemoveHandler _ = () }

[<EntryPoint>]
let main argv =

    let isTestType (t: Type) =
        t.Name.Contains("Abstract") || t.Name.Contains("Concrete")

    let printType (t: Type) =
        printfn "%s" t.Name

        t.GetMethods()
        |> Seq.filter (fun m -> m.Name.Contains("Event"))
        |> Seq.iter (fun m -> printfn "* %s  IsSpecialName = %b" m.Name m.IsSpecialName)

        printfn ""

    Assembly.GetExecutingAssembly().GetExportedTypes()
    |> Seq.filter isTestType
    |> Seq.iter printType

    0

Expected behavior

The program should print IsSpecialName = true for all members, i.e. including those of the abstract types IAbstract1, IAbstract2, and Abstract3.

Actual behavior

The program prints IsSpecialName = true only for accessor methods in types Concrete1 and Concrete2. The accessor methods from the abstract types do not have specialname set.

Known workarounds

None known to me. (Am I possibly missing the correct way to define a CLI event in an abstract type?)

Related information

  • Visual Studio 15.8.8
  • Microsoft Visual F# Tools 10.2 for F# 4.5
  • tested against .NET Core 2.1 and .NET Framework 4.7.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-Compiler-CodeGenIlxGen, ilwrite and things at the backendBugImpact-Low(Internal MS Team use only) Describes an issue with limited impact on existing code.

    Type

    No fields configured for Bug.

    Projects

    Status

    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions