F#“非托管”类型约束的行为

本文关键字:约束 类型 非托管 | 更新日期: 2023-09-27 18:35:32

F# 支持"非托管"的类型约束。这与"结构"约束等值类型约束不同。 MSDN 指出,非托管约束的行为是:

提供的类型必须是非托管类型。非托管类型是某些基元类型(字节、字节、字符、本机、unativeint、float32、float、int16、uint16、int32、uint32、int64、uint64 或十进制)、枚举类型、nativeptr<_>,或者字段都是非托管类型的非泛型结构。

在进行平台调用时,这是一个非常方便的约束类型,我不止一次希望 C# 有一种方法可以做到这一点。C# 没有此约束。C# 不支持可在 CIL 中指定的所有约束。枚举就是这方面的一个例子。在 C# 中,不能执行此操作:

public void Foo<T>(T bar) where T:enum

但是,如果 C# 编译器在另一个库中遇到"枚举"约束,则它确实遵循该约束。Jon Skeet能够使用它来创建他的无约束旋律项目。

所以,我的问题是,F# 的"非托管"约束是可以在 CIL 中表示的东西,比如枚举约束,只是没有在 C# 中公开,还是纯粹由 F# 编译器强制执行,就像 F# 支持的其他一些约束(如显式成员约束)?

F#“非托管”类型约束的行为

我有一些反馈,请注意我对 F# 的了解还不够。 请编辑我去的地方。 首先了解基础知识,运行时实际上并未实现 F# 支持的约束。 并且支持的内容超出了 C# 所支持的范围。 它只有 4 种类型的约束:

  • 必须是引用类型(C# 中的类约束,而不是 F# 中的结构)
  • 必须是值类型(C# 和 F# 中的结构约束)
  • 必须具有默认构造函数(C# 中的 new() 约束,F# 中的 new)
  • 受类型约束。

然后,CLI 规范设置了这些约束如何对特定类型参数类型有效的特定规则,按 ValueType、枚举、委托、数组和任何其他任意类型进行细分。

语言

设计人员可以自由地使用他们的语言进行创新,只要他们遵守运行时可以支持的内容。 他们可以自己添加任意约束,他们有一个编译器来强制执行它们。 或者任意选择不支持运行时支持的语言,因为它不适合他们的语言设计。

只要泛型类型仅在 F# 代码中使用,F# 扩展就可以正常工作。 因此,F# 编译器可以强制执行它。 但是它不能由运行时验证,如果这种类型被另一种语言使用,它根本不会有任何影响。 约束使用 F# 特定属性(Core.CompilationMapping 属性)在元数据中编码,另一种语言编译器知道 bean 应该意味着什么。 在 F# 库中使用所需的非托管约束时,很容易看到:

namespace FSharpLibrary
type FSharpType<'T when 'T : unmanaged>() =
    class end

希望我做对了。 并在 C# 项目中使用:

class Program {
    static void Main(string[] args) {
        var obj = new Example();   // fine
    }
}
class Foo { }
class Example : FSharpLibrary.FSharpType<Foo> { }

编译和执行得很好,实际上根本没有应用约束。 不可能,运行时不支持它。

因此,在 ILDasm 中打开一个小示例,我们看到以下 F# 代码

open System.Collections
type Class1<'T when 'T : unmanaged> =
   class end
type Class2<'T> =
    class end
type Class3<'T when 'T :> IEnumerable> =
    class end

成为以下 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

值得注意的是,Class2有一个不受约束的泛型参数,并且完全匹配Class1,即使T被限制为Class1中的unmanaged。相比之下,Class3 与这种给定模式不匹配,我们可以清楚地看到 IL 中的显式:> IEnumerable约束。

此外,以下 C# 代码

public class Class2<T>
{ }
public class Class3<T>
    where T : IEnumerable
{ }

成为

.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

除了 F# 生成的构造函数 ( .ctor s) 和 Serializable 标志之外,它与 F# 生成的代码匹配。

没有对Class1的其他引用 因此,编译器在 IL 级别不考虑unmanaged约束,并且不会在编译的输出中进一步引用它的存在。

CorHdr.h 中的 CorGenericParamAttr 枚举列出了 CIL 级别所有可能的约束标志,因此非托管约束纯粹由 F# 编译器强制执行。

typedef enum CorGenericParamAttr {
    gpVarianceMask                     =   0x0003,
    gpNonVariant                       =   0x0000, 
    gpCovariant                        =   0x0001,
    gpContravariant                    =   0x0002,
    gpSpecialConstraintMask            =   0x001C,
    gpNoSpecialConstraint              =   0x0000,
    gpReferenceTypeConstraint          =   0x0004, 
    gpNotNullableValueTypeConstraint   =   0x0008,
    gpDefaultConstructorConstraint     =   0x0010
} CorGenericParamAttr;