I'm currently working on an F# library that will be accessed from C#. When doing this the obvious question is; "How will this look from C#?" This wasn't quite clear to me at first so I'll go through a couple custom F# types and show how they are compiled.

Record type, Fermion.fs:

type Spin = 
    | Up = 0
    | Down = 1

type Class = 
    | Quark = 0
    | Lepton = 1

type Type = 
    ...
    | Electron = 6
    ...
type Fermion = 
    { spin: Spin; particleClass: Class; particleType: Type }
    member yada.Spin = yada.spin
    member v.Class = v.particleClass
    member v.Type = v.particleType
    member stuff.GetDescription() = 
        System.String.Format(
            "This {0} is a spin {1} {2}.", 
            stuff.particleType.ToString(), 
            stuff.spin.ToString(), 
            stuff.particleClass.ToString())

Using the record type in F#

let electron = { 
    spin = Spin.Down; 
    particleClass = Class.Lepton; 
    particleType = Type.Electron }

In Reflector:

public static class Fermion
{
    public sealed class Fermion : 
        IStructuralEquatable, 
        IComparable, 
        IStructuralComparable
    {
        internal Particles.Class particleClass@;
        internal Particles.Type particleType@;
        internal Particles.Spin spin@;

        public Fermion(
            Particles.Spin spin, 
            Particles.Class particleClass, 
            Particles.Type particleType)
        {
            this.spin@ = spin;
            this.particleClass@ = particleClass;
            this.particleType@ = particleType;
        }

        public string GetDescription()
        {
            return string.Format("This {0} is a spin {1} {2}.",
                this.particleType@.ToString(), 
                this.spin@.ToString(), 
                this.particleClass@.ToString());
        }

        public Particles.Class Class { get { return this.particleClass@; } }
        public Particles.Class particleClass { get { return this.particleClass@; } }
        public Particles.Type particleType { get { return this.particleType@; } }
        public Particles.Spin spin { get { return this.spin@; } }
        public Particles.Spin Spin { get { return this.spin@; } }
        public Particles.Type Type { get { return this.particleType@; } }

        public int CompareTo(Particles.Fermion obj);
        public sealed override int CompareTo(object obj);
        public sealed override int CompareTo(object obj, IComparer comp);
        public bool Equals(Particles.Fermion obj);
        public sealed override bool Equals(object obj);
        public sealed override bool Equals(object obj, IEqualityComparer comp);
        public override int GetHashCode();
        public sealed override int GetHashCode(IEqualityComparer comp);
    }
}

Some things to note:

  • This is a "record type" so a constructor is generated to set all the private fields. No initialization is performed other than this. So record types are essentially a DTO.
  • It is sealed.
  • When creating an instance of this type it it not necessary to explicitly specify it because of F# type inference.
  • The IComparable, IStructuralComparable and IStructuralEquatable interfaces are all automatically implemented by the F# compiler.
  • A namespace was not explicitly defined in the example so the type has no namespace and the type is nested in a static class with the name of the file it was declared in. If a namespace had been defined then this would not have been nested in a static class and would have simply resided under said namespace.
  • The identifiers ("v", "yada", "stuff") which are equivalent to the "this" keyword in C# are all converted to "this" in the output. And as an aside they do not need to be the same throughout the type, just in the method or property declaration.
  • Fields are publicly exposed via a property that is named as you named the field in the F# source. The fields are suffixed with an "@".
  • As an aside, enumerations must explicitly have a value defined or they will be compiled as discriminated unions.

Now lets mix things up a bit...

Constructed type, Fermion.fs:

namespace Particles

    type Spin = 
        | Up = 0
        | Down = 1

    type Class = 
        | Quark = 0
        | Lepton = 1

    type Type = 
        ...
        | Electron = 6
        ...
    type Fermion(spin: Spin, particleClass: Class, particleType: Type) = 
        let _description = System.String.Format(
                                "This {0} is a spin {1} {2}.", 
                                particleType.ToString(), 
                                spin.ToString(), 
                                particleClass.ToString())
        member yada.Spin = spin
        member v.Class = particleClass
        member v.Type = particleType
        member stuff.GetDescription() = _description

Using the constructed type in F#:

let electron = new Fermion(Spin.Down, Class.Lepton, Type.Electron)

In Reflector:

public class Fermion
{
    internal string _description;
    internal Class particleClass;
    internal Type particleType;
    internal Spin spin;

    public Fermion(Spin spin, Class particleClass, Type particleType)
    {
        this.spin = spin;
        this.particleClass = particleClass;
        this.particleType = particleType;
        this._description = string.Format("This {0} is a spin {1} {2}.", 
                    this.particleType.ToString(), 
                    this.spin.ToString(), 
                    this.particleClass.ToString());
    }

    public string GetDescription() { return this._description; }

    public Class Class {  get { return this.particleClass; } }
    public Spin Spin { get { return this.spin; } }
    public Type Type { get { return this.particleType; } }
}

Some things to note:

  • This is a "constructed type" so there is a constructor. You're constructor code is simply put in the body of the type and not in a special function like C#.
  • Requires you to explicitly new up the type and pass the parameters in.
  • No interfaces get automatically implemented as in the case of the record type.
  • This class is not sealed.
  • This type was defined under a namespace so it is not wrapped in a static class.