Attributs C #: à propos de tous les aspects

Bonjour lecteur. Cet article décrit les attributs de tous les côtés - de la spécification, de la signification et de la définition des attributs, en créant les vôtres et en travaillant avec eux, en terminant par l'ajout d'attributs lors de l'exécution et les attributs existants les plus utiles et intéressants. Si vous êtes intéressé par le sujet des attributs en C #, alors bienvenue dans cat.


Table des matières


  1. Présentation Définition et attribution d'attributs
  2. Attributs intéressants avec support d'exécution. Ici, de brèves informations seront données sur divers attributs, dont peu de gens connaissent l'existence et encore moins qui les utilisent. Puisqu'il s'agit d'informations absolument impraticables, il n'y aura pas beaucoup de diatribes (contrairement à ma passion pour les connaissances inapplicables)
  3. Certains des attributs peu connus qui sont utiles à connaître.
  4. Définir votre attribut et le traiter. Ajout d'attributs au moment de l'exécution

Présentation


Comme toujours, commencez par les définitions et les spécifications. Cela aidera à comprendre et à réaliser les attributs à tous les niveaux, ce qui, à son tour, est très utile pour trouver les bonnes applications pour eux.

Commencez par définir les métadonnées. Les métadonnées sont des données qui décrivent et font référence à des types définis par CTS . Les métadonnées sont stockées d'une manière indépendante de tout langage de programmation particulier. Ainsi, les métadonnées fournissent un mécanisme général d'échange d'informations sur un programme à utiliser entre les outils qui en ont besoin (compilateurs et débogueurs, ainsi que le programme lui-même), ainsi qu'entre VES . Les métadonnées sont incluses dans le manifeste d'assembly. Ils peuvent être stockés dans un fichier PE avec le code IL ou dans un fichier PE séparé, où il n'y aura qu'un manifeste d'assembly.
Un attribut est une caractéristique d'un type ou de ses membres (ou d'autres constructions de langage) qui contient des informations descriptives. Bien que les attributs les plus courants soient prédéfinis et aient un format spécifique dans les métadonnées, des attributs personnalisés peuvent également être ajoutés aux métadonnées. Les attributs sont commutatifs, c'est-à-dire l'ordre de leur déclaration sur l'élément est sans importance

D'un point de vue syntaxique (dans les métadonnées), il existe les attributs suivants

  1. Utilisation d'une syntaxe spéciale dans IL. Par exemple, les mots clés sont des attributs. Et pour eux, il existe une syntaxe spéciale en IL. Il y en a beaucoup; lister tout n'a pas de sens
  2. Utilisation d'une syntaxe généralisée. Il s'agit notamment des attributs d'utilisateur et de bibliothèque.
  3. Attributs de sécurité. Il s'agit notamment des attributs qui héritent de SecurityAttribute (directement ou indirectement). Ils sont traités d'une manière spéciale. Il existe une syntaxe spéciale pour eux dans IL, qui vous permet de créer du xml qui décrit ces attributs directement

Exemple


Code C # contenant tous les types d'attributs ci-dessus
[StructLayout(LayoutKind.Explicit)] [Serializable] [Obsolete] [SecurityPermission(SecurityAction.Assert)] public class Sample { } 


IL résultant
 .class public EXPLICIT ansi SERIALIZABLE beforefieldinit AttributeSamples.Sample extends [System.Runtime]System.Object { .custom instance void [System.Runtime]System.ObsoleteAttribute::.ctor() = (01 00 00 00 ) .permissionset assert = { class 'System.Security.Permissions.SecurityPermissionAttribute, System.Runtime.Extensions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' = {}} .method public hidebysig specialname rtspecialname instance void .ctor() cil managed {/*constructor body*/} } 


Comme vous pouvez le voir, StructLayoutAttribute a une syntaxe spéciale, car en IL, il est représenté comme "explicite". ObsoleteAttribute utilise une syntaxe commune - dans IL commence par ".custom". SecurityPermissionAttribute en tant qu'attribut de sécurité est devenu un ".permissionset assert".

Les attributs utilisateur ajoutent des informations utilisateur aux métadonnées. Ce mécanisme peut être utilisé pour stocker des informations spécifiques à l'application au moment de la compilation et pour y accéder au moment de l'exécution ou pour la lecture et l'analyse par un autre outil. Bien que tout type défini par l'utilisateur puisse être utilisé comme attribut, la conformité CLS requiert que les attributs héritent de System.Attribute. La CLI prédéfinit certains attributs et les utilise pour contrôler le comportement d'exécution. Certaines langues définissent des attributs pour représenter des fonctionnalités de langue non représentées directement dans CTS.

Comme déjà mentionné, les attributs sont stockés dans des métadonnées, qui, à leur tour, sont générées au stade de la compilation, c'est-à-dire saisies dans le fichier PE (généralement * .dll). Ainsi, vous ne pouvez ajouter un attribut au moment de l'exécution qu'en modifiant le fichier exécutable au moment de l'exécution (mais le temps des programmes à changement automatique est révolu depuis longtemps). Il s'ensuit qu'ils ne peuvent pas être ajoutés au stade de l'exécution, mais ce n'est pas tout à fait exact. Si nous formons notre assemblage, y définissons des types, nous pouvons créer un nouveau type au stade de l'exécution et y accrocher des attributs. Donc, formellement, nous pouvons toujours ajouter des attributs au moment de l'exécution (l'exemple sera tout en bas).

Maintenant un peu sur les limites


Si pour une raison quelconque, il y a 2 attributs dans le même assembly avec les noms Name et NameAtribute, il devient impossible de mettre le premier d'entre eux. Lors de l'utilisation de [Nom] (c'est-à-dire sans suffixe), le compilateur indique qu'il voit de l'incertitude. Lorsque vous utilisez [NameAttribute], nous mettrons NameAttribute, ce qui est logique. Il existe une syntaxe spéciale pour une telle situation mystique avec un manque d'imagination lors de la dénomination. Pour mettre la première version sans suffixe, vous pouvez spécifier le signe du chien (c'est-à-dire que [Nom] est une blague, ce n'est pas nécessaire) avant le nom d'attribut [@Name].

Les attributs personnalisés peuvent être ajoutés à tout sauf aux attributs personnalisés. Cela fait référence aux métadonnées, c'est-à-dire si nous mettons un attribut en C # au-dessus de la classe d'attribut, alors dans les métadonnées, il se référera à la classe. Mais vous ne pouvez pas ajouter d'attribut à "public". Mais vous pouvez le faire avec des assemblys, modules, classes, types de valeur, énumérations, constructeurs, méthodes, propriétés, champs, événements, interfaces, paramètres, délégués, valeurs de retour ou paramètres généralisés. L'exemple ci-dessous montre des exemples évidents et peu nombreux de la façon dont vous pouvez mettre un attribut sur une construction particulière.

Syntaxe de déclaration d'attribut
 using System; using System.Runtime.InteropServices; using System.Security.Permissions; using AttributeSamples; [assembly:All] [module:All] namespace AttributeSamples { [AttributeUsage(AttributeTargets.All)] public class AllAttribute : Attribute { } [All] //   public class Usage { [All] //   [return:All] //     public int GiveMeInt<[All]T>([All]int param) { return 5 + param; } [All] //   [field:All] //        public event Action Event; [All] //   [field: All] //       public int Action { get; set; } } } 


Les attributs ont 2 types de paramètres - nommés et positionnels. Les paramètres de position incluent les paramètres du constructeur. To named - propriétés publiques avec un setter accessible. De plus, ce ne sont pas seulement des noms formels; tous les paramètres peuvent être indiqués lors de la déclaration d'un attribut entre crochets après son nom. Les noms sont facultatifs.

Types de paramètres
 [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class AttrParamsAttribute : Attribute { public AttrParamsAttribute(int positional) //  { } public int Named { get; set; } //  } [AttrParams(1)] [AttrParams(1, Named = 2)] public class AttrParams { } 


Les paramètres valides (des deux types) pour l'attribut doivent être l'un des types suivants:

  1. bool, byte, char, double, float, int, long, short, string et plus loin primitive, except decimal
  2. objet
  3. System.Type
  4. énumérer
  5. Un tableau unidimensionnel de l'un des types ci-dessus

Cela est dû en grande partie au fait qu'il devrait s'agir d'une constante au moment de la compilation, et les types ci-dessus peuvent accepter cette constante (en acceptant un objet, nous pouvons passer int). Mais pour une raison quelconque, l'argument ne peut pas être de type ValueType, bien que cela soit possible d'un point de vue logique.

Il existe deux types d'attributs utilisateur: les attributs personnalisés authentiques et les pseudo-personnalisés .
Dans le code, ils se ressemblent (ils sont indiqués au-dessus de la structure du langage entre crochets), mais ils sont traités différemment:

  1. L'attribut utilisateur d'origine est stocké directement dans les métadonnées; les paramètres d'attribut sont stockés tels quels. Ils sont disponibles au moment de l'exécution et sont enregistrés sous forme d'un ensemble d'octets (je m'empresse de vous rappeler qu'ils sont connus au moment de la compilation)
  2. Un attribut pseudo-utilisateur est reconnu car son nom fait partie d'une liste spéciale. Au lieu de stocker ses données directement dans les métadonnées, il est analysé et utilisé pour définir des bits ou des champs dans les tables de métadonnées, puis les données sont supprimées et ne peuvent plus être reçues. Les tables de métadonnées sont vérifiées lors de l'exécution plus rapidement que les attributs utilisateur authentiques, et moins de stockage est requis pour stocker les informations.

Les attributs pseudo-utilisateurs ne sont pas une réflexion visible
 [Serializable] [StructLayout(LayoutKind.Explicit)] public class CustomPseudoCustom { } class Program { static void Main() { var onlyCustom = typeof(CustomPseudoCustom).GetCustomAttributes(); // SerializableAttribute } } 


La plupart des attributs utilisateur sont introduits au niveau de la langue. Ils sont stockés et renvoyés par le runtime, tandis que le runtime ne sait rien de la signification de ces attributs. Mais tous les attributs pseudo-utilisateur ainsi que certains attributs utilisateur présentent un intérêt particulier pour les compilateurs et la CLI. Nous passons donc à la section suivante.

Attributs activés pour l'exécution


Cette section est purement informative, s'il n'y a aucun intérêt à utiliser le runtime, vous pouvez passer à la section suivante.

Le tableau ci-dessous répertorie les attributs pseudo-utilisateur et les attributs utilisateur spéciaux (les CLI ou les compilateurs les traitent de manière spéciale).

Attributs pseudo-utilisateurs (ils ne peuvent pas être obtenus par réflexion).
Attributs CLI:
AttributLa description
AssemblyAlgorithmIDAttributeÉcrit l'identifiant de l'algorithme de hachage utilisé. Définit le champ Assembly.HashAlgId
AssemblyFlagsAttributeÉcrit des drapeaux pour l'assembly correspondant. Définit le champ Assembly.Flags
DllImportAttributeFournit des informations sur le code implémenté dans une bibliothèque non gérée. Définit le bit Method.Flags.PinvokeImpl de la méthode correspondante; ajoute une nouvelle entrée à ImplMap (en définissant les valeurs de MappingFlags, MemberForwarded, ImportName et ImportScope)
StructLayoutAttributeVous permet de définir explicitement la méthode de placement des champs de référence ou de type significatif. Définit le champ TypeDef.Flags.LayoutMask pour le type. Il peut également définir les champs TypeDef.Flags.StringFormatMask, ClassLayout.PackingSize et ClassLayout.ClassSize
FieldOffsetAttributeDéfinit le décalage d'octet des champs dans une référence ou un type significatif. Définit la valeur de FieldLayout.OffSet pour la méthode correspondante.
InattribuerIndique que le paramètre est transmis en tant qu'argument [in]. Définit le bit Param.Flags.In pour le paramètre correspondant.
OutattributeIndique que le paramètre est passé en tant qu'argument [out]. Définit le bit Param.Flags.Out pour le paramètre correspondant.
MarshalasattributeDéfinit comment les données sont marshalées entre le code managé et non managé. Définit le bit Field.Flags.HasFieldMarshal pour le champ (ou le bit Param.Flags.HasFieldMarshal pour le paramètre); Ajoute une entrée à la table FieldMarshal (en définissant les valeurs de Parent et NativeType)
MethodImplAttributeDéfinit les détails d'implémentation d'une méthode. Définit la valeur de Method.ImplFlags pour la méthode correspondante


Attributs CLS - Les langues doivent les prendre en charge:
AttributLa description
AttributeUsageAttributeUtilisé pour indiquer comment un attribut peut être utilisé.
ObsoleteAttributeIndique que l'élément ne doit pas être utilisé.
CLSCompliantAttributeIndique si un élément est déclaré conforme CLS.

Divers intéressant
AttributLa description
ThreadStaticAttributeFournit des champs de type liés au flux
ConditionnelAttributMarque la méthode comme invoquée en fonction d'une condition de compilation (spécifiée dans / define). Si la condition n'est pas remplie, la méthode ne sera pas appelée (et ne sera pas compilée dans IL). Seule la méthode void peut être balisée. Sinon, une erreur de compilation se produira.
DecimalConstantAttributeStocke la valeur constante décimale dans les métadonnées
DefaultMemberAttributeDéfinit le membre de la classe à utiliser par défaut avec la méthode InvokeMember.
CompilationRelaxationsAttributIndique si les exceptions aux vérifications d'instructions sont strictes ou assouplies. Actuellement, vous ne pouvez transmettre que le paramètre NoStringInterning, qui marque l'assembly comme ne nécessitant pas d'internement littéral de chaîne. Mais ce mécanisme peut toujours être utilisé.
DrapeauxAttributAttribut indiquant si l'énumération doit être traitée comme des indicateurs de bit
IndexerNameAttributeSpécifie le nom sous lequel l'indexeur sera connu dans les langages de programmation qui ne prennent pas directement en charge cette fonctionnalité.
ParamArrayAttributeIndique que la méthode accepte un nombre variable de paramètres.

Attributs utiles


Le débogage fait partie intégrante du développement de produits logiciels. Et souvent, dans un système vaste et complexe, il faut des dizaines et des centaines de fois pour exécuter la même méthode et surveiller l'état des objets. Dans le même temps, à un moment de 20, il commence déjà à exaspérer spécifiquement la nécessité d'étendre un objet en profondeur 400 fois pour voir la valeur d'une variable et redémarrer la méthode à nouveau.
Pour un débogage plus silencieux et plus rapide, vous pouvez utiliser des attributs qui modifient le comportement du débogueur.

DebuggerDisplayAttribute indique comment le type ou son membre est affiché dans la fenêtre des variables du débogueur (et pas seulement).

Le seul argument du constructeur est une chaîne avec un format d'affichage. Ce qui sera entre les accolades sera calculé. Le format est comme une chaîne interpolée, mais sans dollar. Vous ne pouvez pas utiliser de pointeurs dans une valeur calculée. Soit dit en passant, si vous avez une ToString remplacée, sa valeur sera affichée comme si elle se trouvait dans cet attribut. S'il existe à la fois une ToString et un attribut, la valeur est extraite de l'attribut.


DebuggerBrowsableAttribute définit la façon dont un champ ou une propriété est affiché dans la fenêtre des variables du débogueur. Accepte un DebuggerBrowsableState, qui a 3 options:

  • Jamais - le champ ne s'affiche pas du tout pendant le débogage. Lorsque vous développez la hiérarchie des objets, ce champ ne sera pas affiché
  • Réduit - le champ n'est pas résolu, mais il peut être développé. Il s'agit du comportement par défaut.
  • RootHidden - le champ lui-même n'est pas affiché, mais les objets qui le composent sont affichés (pour les tableaux et les collections)



DebuggerTypeProxy - si l'objet est affiché dans le débogueur des centaines de fois par jour, vous pouvez être confus et passer 3 minutes à créer un objet proxy qui affiche l'objet d'origine comme il se doit. En règle générale, l'objet proxy à afficher est la classe interne. En fait, il sera affiché à la place de l'objet cible.



Autres attributs utiles

ThreadStatic - un attribut qui vous permet de créer une variable statique pour chaque thread. Pour ce faire, placez l'attribut au-dessus du champ statique. Il convient de rappeler une nuance importante - l'initialisation par un constructeur statique ne sera effectuée qu'une seule fois, et la variable changera dans le thread que le constructeur statique exécutera. Dans le reste, il restera par défaut. (PS. Si vous avez besoin de ce comportement, je vous conseille de regarder vers la classe ThreadLocal).

Un peu sur les nuances du compartiment moteur. Sous Linux et Windows, il existe une zone de mémoire locale au flux ( TLS et TSD, respectivement). Cependant, ces zones elles-mêmes sont très petites. Par conséquent, une structure ThreadLocalInfo est créée, un pointeur vers lequel est placé dans TLS. Par conséquent, un seul emplacement est utilisé. La structure elle-même contient 3 champs - Thread, AppDomain, ClrTlsInfo. Nous nous intéressons au premier. C'est lui qui organise le stockage des statiques de flux en mémoire, en utilisant pour cela ThreadLocalBlock et ThreadLocalModule.

De cette façon:

  • Types de référence - situés sur le tas, ThreadStaticHandleTable, qui est pris en charge par la classe ThreadLocalBlock, conserve des liens vers eux.
  • Structures - Emballées et stockées dans un tas géré ainsi que des types de référence
  • Les types significatifs primitifs sont stockés dans des zones de mémoire non gérée qui font partie de ThreadLocalModule

Eh bien, puisque nous en parlons, il convient de mentionner les méthodes asynchrones. Comme un lecteur attentif le remarquera, si nous utilisons l'asynchronie, la suite ne sera pas nécessairement exécutée dans le même thread (nous pouvons influencer le contexte d'exécution, mais pas le thread). En conséquence, nous obtenons une merde si nous utilisons ThreadLocal. Dans ce cas, il est recommandé d'utiliser AsyncLocal. Mais l'article ne traite pas de cela, nous sommes donc allés plus loin.

InternalsVisibleTo - vous permet de spécifier l'assemblage, qui sera visible pour les éléments marqués internes . Il peut sembler que si une assemblée a besoin de certains types et de leurs membres, vous pouvez simplement les marquer comme publics et non pas steam. Mais une bonne architecture implique de cacher les détails de l'implémentation. Néanmoins, ils peuvent être nécessaires pour certaines choses d'infrastructure, par exemple, des projets de test. En utilisant cet attribut, vous pouvez prendre en charge à la fois l'encapsulation et le pourcentage requis de couverture de test.

HandleProcessCorruptedStateExceptions - vous permet d'effrayer les programmeurs timides et de détecter les exceptions d'un état endommagé. Par défaut, pour de telles exceptions, le CLR n'est pas intercepté. En général, la meilleure solution serait de laisser l'application se bloquer. Ce sont des exceptions dangereuses qui indiquent que la mémoire du processus est corrompue, donc l'utilisation de cet attribut est une très mauvaise idée. Mais il est possible dans certains cas, pour le développement local, il sera utile de définir cet attribut pendant un certain temps. Pour intercepter l'exception d'un état endommagé, placez simplement cet attribut au-dessus de la méthode. Et s'il a déjà atteint l'utilisation de cet attribut, il est recommandé (cependant, comme toujours) d'attraper une exception spécifique.

DisablePrivateReflection - rend tous les membres privés de l'assembly inaccessibles à la réflexion. L'attribut est placé sur l'assemblage.

Définition de votre attribut


Pas seulement parce que cette section est la dernière. Après tout, la meilleure façon de comprendre dans quels cas il sera avantageux d'utiliser l'attribut est de regarder ceux déjà utilisés. Il est difficile de dire une règle formalisée quand vous devez penser à votre propre attribut. Ils sont souvent utilisés comme informations supplémentaires sur un type / son membre ou une autre construction de langage commune à des entités complètement différentes. A titre d'exemple, tous les attributs utilisés pour la sérialisation / ORM / formatage, etc. En raison de l'application étendue de ces mécanismes à des types complètement différents, souvent inconnus des développeurs du mécanisme correspondant, l'utilisation d'attributs est un excellent moyen pour permettre à l'utilisateur de fournir des informations déclaratives pour ce mécanisme.

L'utilisation de vos attributs peut être divisée en 2 parties:

  1. Créer un attribut et l'utiliser
  2. Obtenir un attribut et le traiter

Créer un attribut et l'utiliser


Pour créer votre attribut, il suffit d'hériter de System.Attribute . Dans ce cas, il est conseillé de respecter le style de nommage mentionné - terminez le nom de la classe sur Attribut. Cependant, il n'y aura pas d'erreur si vous omettez ce suffixe. Comme mentionné précédemment, les attributs peuvent avoir 2 types de paramètres - positionnels et nommés. La logique de leur application est la même que pour les propriétés et les paramètres du constructeur de la classe - les valeurs nécessaires pour créer l'objet pour lequel il n'y a pas de "valeur par défaut" raisonnable sont placées en position (c'est-à-dire, constructeur). Ce qui peut être raisonnablement par défaut, qui sera souvent utilisé, est mieux distingué en un nom (c'est-à-dire une propriété).

La limitation de ses lieux d'application n'est pas d'une importance minime dans la création d'un attribut. AttributeUsageAttribute est utilisé pour cela. Le paramètre requis (positionnel) est le AttributeTarget, qui détermine où l'attribut est utilisé (méthode, assemblage, etc.). Les paramètres facultatifs (nommés) sont:

  1. AllowMultiple - indique s'il est possible de placer plus d'un attribut à la place de son application ou non. Faux par défaut
  2. Hérité - détermine si cet attribut appartiendra aux héritiers des classes (en cas de placement sur la classe de base) et aux méthodes remplacées (en cas de placement sur la méthode). La valeur par défaut est vraie.

Après cela, vous pouvez charger les attributs avec une charge utile. Un attribut est une information déclarative, ce qui signifie que tout ce qui y est défini doit décrire la construction à laquelle il se rapporte. L'attribut ne doit contenir aucune logique profonde. Pour le traitement des attributs que vous définissez, des services spéciaux devraient être chargés de les traiter. Mais le fait que l'attribut ne devrait pas avoir de logique ne signifie pas qu'il ne devrait pas avoir de méthodes.

Une méthode (fonction) est également une information et peut également décrire une conception. Et en utilisant le polymorphisme dans les attributs, vous pouvez fournir un outil très puissant et pratique où l'utilisateur peut influencer à la fois les informations utilisées par votre outil et certaines étapes d'exécution et de traitement. , , , . -, , . , , «» .


-. .

. .

ICustomAttributeProvider . Assembly, MemberInfo, Module, ParameterInfo. MemberInfo Type, EventInfo, FieldInfo, MethodBase, PropertyInfo.

3 , . ( , ) ( object). ( , ). CustomAttributeExtensions , , , , . Attribute inherit ( ).

. , , . , bool inherit ( true ). , ( ). , inherit = flase , true
La description
GetCustomAttributes<LogAttribute>(bool inherit). , 1
GetCustomAttribute<LogAttribute>(bool inherit). , System.Reflection.AmbiguousMatchException: Multiple custom attributes of the same type found
GetCustomAttributes()
GetCustomAttributesData()CustomAttributeData, , ( ),
IsDefined(Type attrType, bool inherit)true, , false

.

 [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] public class LogAttribute : Attribute { public string LogName { get; set; } } [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] public class SerializeAttribute : Attribute { public string SerializeName { get; set; } } [Log(LogName = "LogBase1")] [Log(LogName = "LogBase2")] [Serialize(SerializeName = "SerializeBase1")] [Serialize(SerializeName = "SerializeBase2")] public class RandomDomainEntityBase { [Log(LogName = "LogMethod1")] [Log(LogName = "LogMethod2")] [Serialize(SerializeName = "SerializeMethod1")] [Serialize(SerializeName = "SerializeMethod2")] public virtual void VirtualMethod() { throw new NotImplementedException(); } } [Log(LogName = "LogDerived1")] [Log(LogName = "LogDerived2")] [Serialize(SerializeName = "SerializeDerived1")] [Serialize(SerializeName = "SerializeDerived2")] public class RandomDomainEntityDerived : RandomDomainEntityBase { [Log(LogName = "LogOverride1")] [Log(LogName = "LogOverride2")] [Serialize(SerializeName = "SerializeOverride1")] [Serialize(SerializeName = "SerializeOverride2")] public override void VirtualMethod() { throw new NotImplementedException(); } } class Program { static void Main() { Type derivedType = typeof(RandomDomainEntityDerived); MemberInfo overrideMethod = derivedType.GetMethod("VirtualMethod"); // overrideMethod.GetCustomAttribute(typeof(LogAttribute)); //System.Reflection.AmbiguousMatchException: Multiple custom attributes of the same type found. // overrideMethod.GetCustomAttribute<LogAttribute>(false); // System.Reflection.AmbiguousMatchException: Multiple custom attributes of the same type found. // Attribute.GetCustomAttribute(derivedType, typeof(SerializeAttribute)); // System.Reflection.AmbiguousMatchException: Multiple custom attributes of the same type found. IEnumerable<Attribute> allCustom = overrideMethod.GetCustomAttributes(); //LogOverride1 LogOverride2 SerializeOverride1 SerializeOverride2 LogMethod1 LogMethod2 IList<CustomAttributeData> allCustomInfo = overrideMethod.GetCustomAttributesData(); //  ,     IEnumerable<LogAttribute> typedCustom1 = overrideMethod.GetCustomAttributes<LogAttribute>(inherit:false); //LogOverride1 LogOverride2 IEnumerable<LogAttribute> typedInheritedCustom1 = overrideMethod.GetCustomAttributes<LogAttribute>(inherit:true); //LogOverride1 LogOverride2 LogMethod1 LogMethod2 IEnumerable<SerializeAttribute> typedCustom2 = overrideMethod.GetCustomAttributes<SerializeAttribute>(inherit:false); //SerializeOverride1 SerializeOverride2 IEnumerable<SerializeAttribute> typedInheritedCustom2 = overrideMethod.GetCustomAttributes<SerializeAttribute>(inherit:true); //SerializeOverride1 SerializeOverride2 Attribute[] customFromStaticClass = Attribute.GetCustomAttributes(overrideMethod, typeof(SerializeAttribute), inherit:true); //SerializeOverride1 SerializeOverride2 IEnumerable<Attribute> classCustom = derivedType.GetCustomAttributes(); //LogDerived1 LogDerived2 SerializeDerived1 SerializeDerived2 LogBase1 LogBase2 IList<CustomAttributeData> classCustomInfo = derivedType.GetCustomAttributesData(); //  ,     IEnumerable<LogAttribute> typedClassCustom1 = derivedType.GetCustomAttributes<LogAttribute>(false); //LogDerived1 LogDerived2 IEnumerable<LogAttribute> typedInheritedClassCustom1 = derivedType.GetCustomAttributes<LogAttribute>(true); //LogDerived1 LogDerived2 LogBase1 LogBase2 IEnumerable<SerializeAttribute> typedClassCustom2 = derivedType.GetCustomAttributes<SerializeAttribute>(false); //SerializeDerived1 SerializeDerived2 IEnumerable<SerializeAttribute> typedInheritedClassCustom2 = derivedType.GetCustomAttributes<SerializeAttribute>(true); //SerializeDerived1 SerializeDerived2 } } 


. .

 public class TypeCreator { private const string TypeSignature = "DynamicType"; private const string ModuleName = "DynamicModule"; private const string AssemblyName = "DynamicModule"; private readonly TypeBuilder _typeBuilder = GetTypeBuilder(); public object CreateTypeInstance() { _typeBuilder.DefineNestedType("ClassName"); CreatePropertyWithAttribute<SerializeAttribute>("PropWithAttr", typeof(int), new Type[0], new object[0]); Type newType = _typeBuilder.CreateType(); return Activator.CreateInstance(newType); } private void CreatePropertyWithAttribute<T>(string propertyName, Type propertyType, Type[] ctorTypes, object[] ctorArgs) where T : Attribute { var attributeCtor = typeof(T).GetConstructor(ctorTypes); CustomAttributeBuilder caBuilder = new CustomAttributeBuilder(attributeCtor, ctorArgs); PropertyBuilder newProperty = CreateProperty(propertyName, propertyType); newProperty.SetCustomAttribute(caBuilder); } private PropertyBuilder CreateProperty(string propertyName, Type propertyType) { FieldBuilder fieldBuilder = _typeBuilder.DefineField(propertyName, propertyType, FieldAttributes.Private); PropertyBuilder propertyBuilder = _typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; MethodBuilder getter = GenerateGetter(); MethodBuilder setter = GenerateSetter(); propertyBuilder.SetGetMethod(getter); propertyBuilder.SetSetMethod(setter); return propertyBuilder; MethodBuilder GenerateGetter() { MethodBuilder getMethodBuilder = _typeBuilder.DefineMethod($"get_{propertyName}", getSetAttr, propertyType, Type.EmptyTypes); ILGenerator getterIl = getMethodBuilder.GetILGenerator(); getterIl.Emit(OpCodes.Ldarg_0); getterIl.Emit(OpCodes.Ldfld, fieldBuilder); getterIl.Emit(OpCodes.Ret); return getMethodBuilder; } MethodBuilder GenerateSetter() { MethodBuilder setMethodBuilder = _typeBuilder.DefineMethod($"set_{propertyName}", getSetAttr, null,new [] { propertyType }); ILGenerator setterIl = setMethodBuilder.GetILGenerator(); setterIl.Emit(OpCodes.Ldarg_0); setterIl.Emit(OpCodes.Ldarg_1); setterIl.Emit(OpCodes.Stfld, fieldBuilder); setterIl.Emit(OpCodes.Ret); return setMethodBuilder; } } private static TypeBuilder GetTypeBuilder() { var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(AssemblyName), AssemblyBuilderAccess.Run); ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(ModuleName); TypeBuilder typeBuilder = moduleBuilder.DefineType(TypeSignature, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout,null); return typeBuilder; } } public class Program { public static void Main() { object instance = new TypeCreator().CreateTypeInstance(); IEnumerable<Attribute> attrs = instance.GetType().GetProperty("PropWithAttr").GetCustomAttributes(); // Serializable } } 

Source: https://habr.com/ru/post/fr468287/


All Articles