Поведение ограничения типа F# "неуправляемый"

F# поддерживает ограничение типа для "неуправляемых". Это не то же самое, что ограничение типа значения, например ограничения "struct". MSDN Примечания что поведение неуправляемого ограничения:

предоставленный тип должен быть неуправляемым типом. Неуправляемые типы являются либо некоторые примитивные типы (тип sbyte, байт, символ, nativeint, unativeint, float32, поплавок, типа INT16, типа uint16, int32 и тип uint32, типа int64, uint64 В или decimal), типы перечисления, nativeptr, или неродовая структура, поля которой являются неуправляемыми типами.

это очень удобный тип ограничения при вызове платформы, и несколько раз я хотел бы, чтобы C# имел способ сделать это. C# не имеет этого ограничения. C# не поддерживает все ограничения, что can указывается в CIL. Примером этого является перечисление. В C# вы не можете этого сделать:

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

однако компилятор C# выполняет ограничение" enum", если он сталкивается с ним в другой библиотеке. Джон Скит может использовать это, чтобы создать его Непринужденная Мелодия.

Итак, мой вопрос, является F#'ы "неуправляемый" ограничение-то, что может быть представлено в КСС, как перечисление ограничений и просто не выставляли в C#, или это действие чисто компилятор F#, как некоторые другие ограничения F# поддерживает (как явным членом ограничение)?

3 ответов


у меня есть обратная связь, остерегайтесь, что я не знаю F# достаточно хорошо. Пожалуйста, отредактируйте, где я лох. Во-первых, среда выполнения фактически не реализует ограничения, поддерживаемые F#. И поддерживает больше, чем поддерживает C#. Он имеет только 4 типа ограничений:

  • должен быть ссылочным типом (ограничение класса В C#, а не struct в F#)
  • должно быть типом значения (ограничение структуры в C# и F#)
  • должен иметь значение по умолчанию ограничение конструктора (new () В C#, new В F#)
  • ограничено по типу.

и спецификация CLI затем устанавливает конкретные правила о том, как эти ограничения могут быть действительны для определенного типа параметра типа, разбитого на ValueType, Enum, Delegate, Array и любой другой произвольный тип.

языковые дизайнеры могут свободно внедрять инновации на своем языке, если они соблюдают то, что может поддерживать среда выполнения. Они могут добавлять произвольные ограничения сами по себе, у них есть компилятор, чтобы принудить их. Или произвольно выбрать не поддерживать тот, который поддерживает среда выполнения, потому что он не соответствует их языковому дизайну.

расширения F# работают нормально, пока общий тип используется только в коде F#. Таким образом, компилятор F# может применить его. Но он не может быть проверен средой выполнения и не будет иметь никакого эффекта, если такой тип используется другим языком. Ограничение кодируется в метаданных с определенными атрибутами F# (Сердечник.Compilationmapping attribute), другой компилятор языка знает бобы, что они должны означать. Легко видно, когда вы используете неуправляемое ограничение, которое вам нравится в библиотеке 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 ограничен unmanaged на Class1. Напротив,Class3 не соответствует этому образцу, и мы можем ясно видеть явные :> IEnumerable ограничение в IL.

кроме того, следующий 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#-генерируемых конструкторов (.ctors) и Serializable флаги, соответствует F# сгенерированный код.

без каких-либо других ссылок на Class1 таким образом, означает, что компилятор на уровне IL не учитывает unmanaged ограничение и не оставляйте дальнейших ссылок на его присутствие в скомпилированном выводе.


на Перечисление CorGenericParamAttr В CorHdr.h перечисляет все возможные флаги ограничений на уровне CIL, поэтому неуправляемое ограничение выполняется компилятором F#.

typedef enum CorGenericParamAttr {
    gpVarianceMask                     =   0x0003,
    gpNonVariant                       =   0x0000, 
    gpCovariant                        =   0x0001,
    gpContravariant                    =   0x0002,

    gpSpecialConstraintMask            =   0x001C,
    gpNoSpecialConstraint              =   0x0000,
    gpReferenceTypeConstraint          =   0x0004, 
    gpNotNullableValueTypeConstraint   =   0x0008,
    gpDefaultConstructorConstraint     =   0x0010
} CorGenericParamAttr;