Exploring Custom Type Interoperability between F# and C# February, 2010
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.