F#"​​非托管"类型约束的行为

vcs*_*nes 20 c# f# cil

F#支持"非托管"的类型约束.这与"struct"约束之类的值类型约束不同. MSDN注意到非托管约束的行为是:

提供的类型必须是非托管类型.非托管类型是某些基本类型(sbyte,byte,char,nativeint,unativeint,float32,float,int16,uint16,int32,uint32,int64,uint64或decimal),枚举类型,nativeptr <_>或非通用结构,其字段都是非托管类型.

这是一个非常方便的约束类型,在进行平台调用时,我不止一次希望C#有办法做到这一点.C#没有这个约束.C#不支持在CIL中指定的所有约束.一个例子是枚举.在C#中,你不能这样做:

public void Foo<T>(T bar) where T:enum
Run Code Online (Sandbox Code Playgroud)

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

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

Han*_*ant 6

我得到了一些反馈,请注意我不太了解F#.请编辑我的位置.首先了解基础知识,运行时实际上并没有实现F#支持的约束.并且支持的不仅仅是C#支持的功能.它只有4种类型的约束:

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

然后,CLI规范就这些约束如何在特定类型参数类型上有效设置特定规则,按ValueType,Enum,Delegate,Array和任何其他任意类型进行细分.

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

只要泛型类型只用于F#代码,F#扩展就可以正常工作.所以F#编译器可以强制执行它.但它无法通过运行时验证,如果这种类型被另一种语言使用,它根本不会产生任何影响.约束使用F#特定属性(Core.CompilationMapping属性)编码在元数据中,另一种语言编译器知道bean应该是什么意思.在F#库中使用您喜欢的非托管约束时,可以轻松看到:

namespace FSharpLibrary

type FSharpType<'T when 'T : unmanaged>() =
    class end
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 请注意,虽然这是一个很好的例子,你应该使用`class Foo`,因为一个空的`struct`符合`unmanaged`约束的定义(一个`struct`只包含非托管类型) (3认同)

Dav*_*vid 5

因此,在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
Run Code Online (Sandbox Code Playgroud)

成为以下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
Run Code Online (Sandbox Code Playgroud)

值得注意的是,Class2有一个不受约束的泛型参数,并完全符合Class1,即使T被约束unmanagedClass1.相比之下,Class3与这个给定的模式不匹配,我们可以清楚地看到:> IEnumerableIL中的显式约束.

另外,还有以下C#代码

public class Class2<T>
{ }

public class Class3<T>
    where T : IEnumerable
{ }
Run Code Online (Sandbox Code Playgroud)

.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
Run Code Online (Sandbox Code Playgroud)

除了F#生成的构造函数.ctorSerializable标志之外,它与F#生成的代码匹配.

没有其他引用Class1因此意味着编译器不在IL级别考虑unmanaged约束,并且不会在编译输出中留下其存在的进一步引用.


the*_*nyy 5

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;
Run Code Online (Sandbox Code Playgroud)