F# supports a type constraint for \"unmanaged\". This is not the same as a value type constraint like \"struct\" constraints. MSDN notes that the behavior of the unmanaged
I've got some feedback, beware that I don't know F# nearly well enough. Please edit where I goof. Getting to the basics first, the runtime does not actually implement the constraints that F# supports. And supports more than what C# supports. It has just 4 types of constraints:
And the CLI specification then sets specific rules on how these constraints can be valid on a specific type parameter type, broken down by ValueType, Enum, Delegate, Array and any other arbitrary type.
Language designers are free to innovate in their language, as long as they abide by what the runtime can support. They can add arbitrary constraints by themselves, they have a compiler to enforce them. Or arbitrarily choose to not support one that the runtime supports because it doesn't fit their language design.
The F# extensions work fine as long as the generic type is only ever used in F# code. So the F# compiler can enforce it. But it cannot be verified by the runtime and it will not have any effect at all if such a type is consumed by another language. The constraint is encoded in the metadata with F# specific attributes (Core.CompilationMapping attribute), another language compiler knows beans what they are supposed to mean. Readily visible when you use the unmanaged constraint you like in an F# library:
namespace FSharpLibrary
type FSharpType<'T when 'T : unmanaged>() =
class end
Hope I got that right. And used in a C# project:
class Program {
static void Main(string[] args) {
var obj = new Example(); // fine
}
}
class Foo { }
class Example : FSharpLibrary.FSharpType<Foo> { }
Compiles and executes just fine, the constraint is not actually applied at all. It can't be, the runtime doesn't support it.
The CorGenericParamAttr Enumeration in CorHdr.h lists all possible constraint flags at CIL level, so an unmanaged constraint is purely enforced by the F# compiler.
typedef enum CorGenericParamAttr {
gpVarianceMask = 0x0003,
gpNonVariant = 0x0000,
gpCovariant = 0x0001,
gpContravariant = 0x0002,
gpSpecialConstraintMask = 0x001C,
gpNoSpecialConstraint = 0x0000,
gpReferenceTypeConstraint = 0x0004,
gpNotNullableValueTypeConstraint = 0x0008,
gpDefaultConstructorConstraint = 0x0010
} CorGenericParamAttr;
So, opening a small sample in ILDasm, we see the following F# code
open System.Collections
type Class1<'T when 'T : unmanaged> =
class end
type Class2<'T> =
class end
type Class3<'T when 'T :> IEnumerable> =
class end
becomes the following IL
.class public auto ansi serializable beforefieldinit FSharpLibrary.Class1`1<T>
extends [mscorlib]System.Object
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
} // end of class FSharpLibrary.Class1`1
.class public auto ansi serializable beforefieldinit FSharpLibrary.Class2`1<T>
extends [mscorlib]System.Object
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
} // end of class FSharpLibrary.Class2`1
.class public auto ansi serializable beforefieldinit FSharpLibrary.Class3`1<([mscorlib]System.Collections.IEnumerable) T>
extends [mscorlib]System.Object
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 )
} // end of class FSharpLibrary.Class3`1
Notably, Class2
has an unconstrained generic parameter, and perfectly matches Class1
even though T
is constrained to unmanaged
in Class1
. By contrast, Class3
does not match this given pattern, and we can clearly see the explicit :> IEnumerable
constraint in IL.
In addition, the following C# code
public class Class2<T>
{ }
public class Class3<T>
where T : IEnumerable
{ }
Becomes
.class public auto ansi beforefieldinit CSharpLibrary.Class2`1<T>
extends [mscorlib]System.Object
{
} // end of class CSharpLibrary.Class2`1
.class public auto ansi beforefieldinit CSharpLibrary.Class3`1<([mscorlib]System.Collections.IEnumerable) T>
extends [mscorlib]System.Object
{
} // end of class CSharpLibrary.Class3`1
Which, with the exception of the F#-generated constructors (.ctor
s) and Serializable
flags, matches the F# generated code.
With no other references to Class1
Thus means that the compiler is not, at the IL level, taking into account the unmanaged
constraint, and leave no futher references to its presence in the compiled output.