Skip to content

C# record interop  #904

@jbtule

Description

@jbtule

I'm not sure what the current interop plan for F# is with init only properties in general is?
I assume given a C# class:

   public class CSharpClass{
       public string? Prop1 {get; init;}
       public string? Prop2 {get; init;}
   }

it's using the existing F# init properties syntax

let test = CSharpClass(Prop1="Test", Prop2="Test2")

However, I tested in .net 5.0 preview 7 and the F# compiler has no errors, but builds an "Invalid Program" according to the CLR.
Also with the simpler case of initializing inside a constructor.

type TestFsharp() as this= 
    inherit CSharpClass ()
    do 
        this.Prop1 <- "Yo"
        this.Prop2 <- "What"

Also

Unhandled exception. System.InvalidProgramException: Common Language Runtime detected an invalid program.
   at Program.TestFsharp..ctor()
   at Program.main(String[] argv) 

If you try to mutate an init only property outside an initialization block, it also compiles without error, but throws a runtime error. So at the very least that should be a compiler error, and thus am sure .net 5.0 preview 7 F# does not do anything yet for init only properties.

I thought I would take the time to make suggestions for the interop story with 3 cases of init only properties from C#.

So the General Case is a random C# class with an init only setter.

public class CSharpClass{
    public string? Prop1 {get; init;}
    public string? Prop2 {get; init;}
}

Probably handed by the previously mentioned CSharpClass(Prop1="Test", Prop2="Test2") and should work from constructor too.

Then there are 2 C# Record cases. Luckily not allowing inheritance of these records from F# seems like a legit path and would simplify things.

The most attractive C# Record types for developers are the positional records with a primary constructor.

public record CSharpRecord(string Prop1, string Prop2);

With these you may not even need to to use the init only properties in F# because you could use F# Record syntax {Prop1="Test; Prop2="Test2"} and it would be very similar to how you initialize F# records, and Copy them. {source with Prop1="Test1"}

However C# Records don't need to have a primary constructor.

// with empty constructor
public record CSharpRecord {
    public string? Prop1 {get; init;}
    public string? Prop2 {get; init;}
}
//rando constructor
public record CSharpRecord {
    public CSharpRecord(bool rando){
        Prop1 = rando.ToString();
    }
    public string? Prop1 {get; init;}
    public string? Prop2 {get; init;}
}

And then I suppose you'd be left with the general case CSharpRecord(Prop1="Test", Prop2="Test2"), or if there was an empty constructor you could might be able to use the Record syntax safely if you can identify it as a C# Record. And with any record, if you can identify it, it should still possible to syntactically supporting copy {source with Prop1="Test1"} but you'd have to use the <Clone>$ method and init only properties the way C# does. It may be preferred to do this for Positional Records with Primary Constructors, I'm not sure.

Pros and Cons

The advantages of making this adjustment to F# are compiling functioning programs when consuming a C# type with init only properties!

The disadvantages of making this adjustment to F# is its needed for .net 5.0 which is coming up fast! At the very least needs compiler errors.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): ?????

Links:

C# 9.0 init only Proposal (mentions F# would be changed):
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/init.md

C# 9.0 Records Proposal:
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md

Related Suggestions:

#903

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions