net msil c# f# cil

c# - msil - cil net



Comportamiento de la restricción tipo F#"no administrada" (3)

F # admite una restricción de tipo para "no administrado". Esto no es lo mismo que una restricción de tipo de valor como restricciones de "estructura". MSDN observa que el comportamiento de la restricción no administrada es:

El tipo proporcionado debe ser un tipo no administrado. Los tipos no administrados son ciertos tipos primitivos (sbyte, byte, char, nativeint, unativeint, float32, float, int16, uint16, int32, uint32, int64, uint64 o decimal), tipos de enumeración, nativeptr <_>, o un no Estructura genérica cuyos campos son todos tipos no gestionados.

Este es un tipo de restricción muy útil cuando se invoca la plataforma, y ​​más de una vez me gustaría que C # tuviera una forma de hacerlo. C # no tiene esta restricción. C # no admite todas las restricciones que se pueden especificar en CIL. Un ejemplo de esto es una enumeración. En C #, no puedes hacer esto:

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

Sin embargo, el compilador de C # respeta la restricción "enum" si se encuentra en otra biblioteca. Jon Skeet puede usar esto para crear su proyecto Melody sin restricciones .

Entonces, mi pregunta es si la restricción "no administrada" de F # es algo que se puede representar en CIL, como una restricción de enumeración y simplemente no se expone en C #, o si el compilador de F # la impone simplemente como algunas de las otras restricciones que F # admite (como Restricción explícita de miembro)?


Entonces, al abrir una pequeña muestra en ILDasm, vemos el siguiente código 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

se convierte en la siguiente 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

En particular, Class2 tiene un parámetro genérico sin restricciones, y coincide perfectamente con Class1 , aunque T está restringido a unmanaged en Class1 . Por el contrario, Class3 no coincide con este patrón dado, y podemos ver claramente la restricción explícita :> IEnumerable en IL.

Además, el siguiente código C #

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

Se convierte

.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

El cual, con la excepción de los constructores generados por F # ( .ctor s) y las banderas Serializable , coincide con el código generado por F #.

Sin otras referencias a Class1 , esto significa que el compilador no está, en el nivel de IL, teniendo en cuenta la restricción unmanaged , y no deja más referencias a su presencia en la salida compilada.


La enumeración CorGenericParamAttr en CorHdr.h enumera todos los posibles indicadores de restricción a nivel de CIL, por lo que el compilador F # impone una restricción no administrada.

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


Tengo algunos comentarios, tenga cuidado de no conocer F # lo suficientemente bien. Por favor, edita donde yo hago. Al llegar a lo básico primero, el tiempo de ejecución no implementa realmente las restricciones que admite F #. Y soporta más de lo que soporta C #. Tiene solo 4 tipos de restricciones:

  • debe ser un tipo de referencia (restricción de clase en C #, no estructura en F #)
  • debe ser un tipo de valor (restricción de estructura en C # y F #)
  • debe tener un constructor predeterminado (nueva () restricción en C #, nuevo en F #)
  • Restringido por el tipo.

Y la especificación de CLI luego establece reglas específicas sobre cómo estas restricciones pueden ser válidas en un tipo de parámetro de tipo específico, desglosado por ValueType, Enum, Delegate, Array y cualquier otro tipo arbitrario.

Los diseñadores de idiomas son libres de innovar en su idioma, siempre y cuando cumplan con lo que el tiempo de ejecución puede soportar. Pueden agregar restricciones arbitrarias por sí mismos, tienen un compilador para hacerlas cumplir. O elija arbitrariamente no admitir uno que admita el tiempo de ejecución porque no se ajusta al diseño de su idioma.

Las extensiones F # funcionan bien siempre y cuando el tipo genérico solo se use en el código F #. Así que el compilador de F # puede imponerlo. Pero no puede ser verificado por el tiempo de ejecución y no tendrá ningún efecto si ese tipo es consumido por otro idioma. La restricción está codificada en los metadatos con atributos específicos de F # (atributo Core.CompilationMapping), otro compilador de idioma sabe lo que se supone que significan. Fácilmente visible cuando usa la restricción no administrada que le gusta en una biblioteca F #:

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

Espero haberlo hecho bien. Y utilizado en un proyecto de C #:

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

Compila y ejecuta bien, la restricción no se aplica en absoluto. No puede ser, el tiempo de ejecución no lo soporta.