question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

F# implemented abstract static member causes Bad image format invoked through reflection

See original GitHub issue

Repro steps

In order to reproduce the issue I create an implementation of IParsable from F#, then invoke it through reflection

open System
open System.ComponentModel

// from https://madskristensen.net/post/A-shorter-and-URL-friendly-GUID
let toStr (x : Guid) = 
    let enc = Convert.ToBase64String(x.ToByteArray())
    let enc = enc.Replace("/", "_").Replace("+", "-")
    enc.Substring(0, 22)

let fromStr (encoded : string) = 
    let encoded = encoded.Replace("_", "/").Replace("-", "+")
    let buffer = Convert.FromBase64String(encoded + "==")
    if buffer.Length = 16 then Some(Guid buffer)
    else None

let tryParseId (prefix : string) (str : string) = 
    if str.StartsWith(prefix) then fromStr (str.Substring prefix.Length)
    else None

let parseId prefix str = 
    let maybeParsed = tryParseId prefix str
    match maybeParsed with
    | Some g -> g
    | None -> raise (FormatException str)

type CustomerId = 
    { Value : Guid }
    static member Default : CustomerId = { Value=Guid.Empty }
    static member Parse(str : string) : CustomerId = { Value = parseId "c-" str }
    override this.ToString() = sprintf "c-%s" (toStr this.Value)
    interface IParsable<CustomerId> with
        static member Parse(s:string, f:IFormatProvider) = CustomerId.Parse(s)
        static member TryParse(s:string, f:IFormatProvider, result:byref<CustomerId>) =
            try
                result <- CustomerId.Parse(s)
                true
            with _ ->
                result <- Unchecked.defaultof<_>
                false
typeof<CustomerId>.GetInterface("IParsable`1").GetMethod("Parse").Invoke(null, [| "c-ReJ_cYj8l0WmonC0k0hQvw"; null |]);;
(**
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.BadImageFormatException: Bad IL format.
   at InvokeStub_IParsable`1.Parse(Object, Object, IntPtr*)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at <StartupCode$FSI_0030>.$FSI_0030.main@() in /Users/mathieu/src/cs/Saithe/stdin:line 158
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)
Stopped due to error
*)

Expected behavior

If you implement the same logic but with an instance interface then you get the expected behaviour:

type IParsableInst<'T> = interface
    abstract member Parse : string -> 'T
    abstract member TryParse : string*byref<'T> -> bool
end

type CustomerId2 = 
    { Value2 : Guid }
    static member Default : CustomerId2 = { Value2=Guid.Empty }
    static member Parse(str : string) : CustomerId2 = { Value2 = parseId "c-" str }
    override this.ToString() = sprintf "c-%s" (toStr this.Value2)
    interface IParsableInst<CustomerId2> with
        member this.Parse(s:string) = CustomerId2.Parse(s)
        member this.TryParse(s:string, result:byref<CustomerId2>) =
            try
                result <- CustomerId2.Parse(s)
                true
            with _ ->
                result <- Unchecked.defaultof<_>
                false

typeof<CustomerId2>.GetInterface("IParsableInst`1").GetMethod("Parse").Invoke(CustomerId2.Default, [| "c-ReJ_cYj8l0WmonC0k0hQvw" |]);;
// val it: obj = { Value2 = 717fe245-fc88-4597-a6a2-70b4934850bf }

Actual behavior

Throws System.BadImageFormatException

Known workarounds

It’s a new feature in .net.

Related information

  • Operating system (Windows Appveyor Visual Studio 2022 image, Mac OS X, GitHub Ubuntu latest (at the time of writing 2022-11) )
  • .NET Runtime kind (.NET 7.0.100)

Issue Analytics

  • State:closed
  • Created 10 months ago
  • Comments:18 (18 by maintainers)

github_iconTop GitHub Comments

2reactions
wallymathieucommented, Nov 25, 2022

Unless they have the same issue?

1reaction
wallymathieucommented, Dec 7, 2022

Thanks! I’ll report it to the runtime repo!

Read more comments on GitHub >

github_iconTop Results From Across the Web

BadImageFormatException when invoking static abstract ...
The expected behaviour would be to match the behaviour of regular instance methods on interfaces (you can invoke them through reflection).
Read more >
C# Template Function: Accessing static abstract members ...
C# Template Function: Accessing static abstract members throws System. BadImageFormatException - Stack Overflow.
Read more >
Can we declare the static variables and methods in an ...
Yes, an abstract class can have static variables and static methods: ... Deeksha and Bharti both stated that static class members are shareable...
Read more >
Static abstract members in interfaces - .NET
Learn about the .NET 6 breaking change where `static` interface members can now be marked `abstract`.
Read more >
Static Keyword - Manual
Static properties are accessed using the Scope Resolution Operator ( :: ) and cannot be accessed through the object operator ( -> )....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found