Comportement de la contrainte de type «non géré» F #

F # supporte une contrainte de type pour “unmanaged”. Ce n’est pas la même chose qu’une contrainte de type valeur telle que les contraintes “struct”. MSDN note que le comportement de la contrainte non gérée est le suivant:

Le type fourni doit être un type non géré. Les types non gérés sont soit certains types primitifs (sbyte, byte, char, nativeint, unativeint, float32, float, int16, uint16, int32, uint32, int64, uint64 ou décimal), des types d’énumération, nativeptr , ou une non structure générique dont les champs sont tous des types non gérés.

Il s’agit d’un type de contrainte très pratique lors de l’appel de la plate-forme, et je souhaite plus d’une fois que C # ait le moyen de le faire. C # n’a pas cette contrainte. C # ne prend pas en charge toutes les contraintes pouvant être spécifiées dans CIL. Un exemple de ceci est une énumération. En C #, vous ne pouvez pas faire ceci:

public void Foo(T bar) where T:enum 

Cependant, le compilateur C # respecte la contrainte “enum” s’il la trouve dans une autre bibliothèque. Jon Skeet est capable de l’utiliser pour créer son projet Unconstrained Melody .

Ma question est donc la suivante: la contrainte “non gérée” de F # peut-elle être représentée dans CIL, comme une contrainte enum et n’est pas exposée en C #, ou est-elle appliquée uniquement par le compilateur F #, comme certaines des autres contraintes supscopes par F # (comme Contrainte de membre explicite)?

J’ai quelques retours, méfiez-vous que je ne connais pas assez bien le F #. S’il vous plaît éditer où je goof. Pour commencer, le moteur d’exécution n’implémente pas les contraintes sockets en charge par F #. Et prend en charge plus que ce que C # prend en charge. Il a juste 4 types de contraintes:

  • doit être un type de référence (contrainte de classe en C #, pas de structure en F #)
  • doit être un type valeur (contrainte de struct en C # et F #)
  • doit avoir un constructeur par défaut (contrainte new () en C #, nouvelle en F #)
  • contraint par type.

Et la spécification CLI définit ensuite des règles spécifiques sur la manière dont ces contraintes peuvent être valides sur un type de paramètre de type spécifique, ventilées par ValueType, Enum, Delegate, Array et tout autre type arbitraire.

Les concepteurs de langage sont libres d’innover dans leur langue, dans la mesure où ils respectent ce que le moteur d’exécution peut prendre en charge. Ils peuvent append des contraintes arbitraires eux-mêmes, ils ont un compilateur pour les appliquer. Ou choisissez de manière arbitraire de ne pas prendre en charge un logiciel pris en charge par le moteur d’exécution, car cela ne correspond pas à la conception de leur langage.

Les extensions F # fonctionnent correctement tant que le type générique n’est utilisé que dans le code F #. Ainsi, le compilateur F # peut l’appliquer. Mais il ne peut pas être vérifié par le moteur d’exécution et n’aura aucun effet si un tel type est utilisé par une autre langue. La contrainte est codée dans les métadonnées avec des atsortingbuts spécifiques à F # (atsortingbut Core.CompilationMapping), un autre compilateur de langage connaît les beans de ce qu’ils sont censés signifier. Facilement visible lorsque vous utilisez la contrainte non managée de votre choix dans une bibliothèque F #:

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

J’espère que j’ai bien compris. Et utilisé dans un projet C #:

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

Comstack et exécute parfaitement, la contrainte n’est pas appliquée du tout. Cela ne peut pas être, le runtime ne le supporte pas.

Donc, ouvrant un petit échantillon dans ILDasm, nous voyons le code F # suivant

 open System.Collections type Class1<'T when 'T : unmanaged> = class end type Class2<'T> = class end type Class3<'T when 'T :> IEnumerable> = class end 

devient l’IL suivant

 .class public auto ansi serializable beforefieldinit FSharpLibrary.Class1`1 extends [mscorlib]System.Object { .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAtsortingbute::.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 extends [mscorlib]System.Object { .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAtsortingbute::.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.CompilationMappingAtsortingbute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 03 00 00 00 00 00 ) } // end of class FSharpLibrary.Class3`1 

Notamment, Class2 a un paramètre générique non contraint et correspond parfaitement à Class1 même si T est contraint à unmanaged dans Class1 . En revanche, Class3 ne correspond pas à ce modèle, et nous pouvons clairement voir la contrainte explicite :> IEnumerable dans IL.

De plus, le code C # suivant

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

Devient

 .class public auto ansi beforefieldinit CSharpLibrary.Class2`1 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 

Qui, à l’exception des constructeurs générés par F # ( .ctor s) et des indicateurs Serializable , correspond au code généré par F #.

Sans autre référence à Class1 Cela signifie donc que le compilateur n’est pas, au niveau IL, prenant en compte la contrainte unmanaged et ne laisse aucune référence supplémentaire à sa présence dans la sortie compilée.

L’ énumération CorGenericParamAttr dans CorHdr.h répertorie tous les indicateurs de contrainte possibles au niveau CIL, de sorte qu’une contrainte non gérée est ssortingctement appliquée par le compilateur F #.

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