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

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

Предоставленный тип должен быть неуправляемым типом. Неуправляемые типы - это определенные примитивные типы (sbyte, byte, char, nativeint, 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# соблюдает ограничение enum, если сталкивается с ним в другой библиотеке. Джон Скит может использовать это для создания своего проекта Unconstrained Melody.

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

3 ответа

Решение

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

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

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

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

Расширения F# работают нормально, если универсальный тип используется только в коде F#. Таким образом, компилятор F# может применять его. Но это не может быть проверено средой выполнения и не будет иметь никакого эффекта, если такой тип используется другим языком. Ограничение кодируется в метаданных с помощью специфических атрибутов F# (атрибут Core.CompilationMapping), другой языковой компилятор знает бины, что они должны означать. Легко увидеть, когда вы используете неуправляемое ограничение, которое вам нравится в библиотеке 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#-генерированных конструкторов (.ctorс) и Serializable flags, соответствует 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;
Другие вопросы по тегам