waiwai933
waiwai933

Reputation: 14559

Implementing C# interfaces with nullable reference types in F#

I'm trying to learn F# by converting an existing .NET Core solution in C# over, one project at a time. I currently have an interface in C# with nullable reference types:

public interface IVehicle {
    public int GetWheels();
    public string? GetLicensePlate();
}

And this interface is implemented in a number of other projects which depend on this one. I'm trying to convert one of these over, but if I try

type Car(serialNumber: string) =
    interface MyProjectInterfaces.IVehicle with
        member this.GetWheels() = 4
        member this.GetLicensePlate() = 
            match LicensePlateService.GetLicencePlate(serialNumber) with
                | Some(x) -> System.Nullable(x)
                | None -> System.Nullable()

I get the error:

This expression was expected to have type 'string' but here has type 'Nullable<string>'

This doesn't seem to affect value types, so I'm assuming it's something to do with string being a reference type.

What do I do to solve this? Presumably I could rewrite the underlying interface to use F# and thus options, but there are other C# projects that implement the interface and I don't want to have to rewrite the whole solution in one go. Or am I doing F# completely wrong?

Upvotes: 1

Views: 824

Answers (1)

Asti
Asti

Reputation: 12667

This is a confusion between C# 8's nullable references and nullable value types, aka Nullable<T>. If you look at the definition of Nullable<T>, you'll find:

public struct Nullable<T> where T : struct

which means it's only for value types. int? is short for Nullable<int>. That's different from nullable references.

The nullability modifier for reference types doesn’t introduce a new type. Reference types are still nullable and compiling string? results in IL that’s still just System.String.

The difference at the IL level is the decoration of nullable modified types with a NullableAttribute.

In other words, it's just a compiler construct - one which isn't visible to F#.

match LicensePlateService.GetLicencePlate(serialNumber) with
| Some(x) -> x
| None -> null

will be the correct, albeit non-idiomatic replacement.

Upvotes: 3

Related Questions