Поведение "неуправляемого" типа ограничения 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;