C # -Attribute: Über alle Aspekte

Hallo Leser. Dieser Artikel beschreibt Attribute von allen Seiten - angefangen bei der Spezifikation, Bedeutung und Definition von Attributen über das Erstellen eigener Attribute und deren Arbeit bis hin zum Hinzufügen von Attributen zur Laufzeit und den nützlichsten und interessantesten vorhandenen Attributen. Wenn Sie sich für das Thema Attribute in C # interessieren, sind Sie bei cat willkommen.


Inhalt


  1. Einführung Attribute definieren und zuweisen
  2. Interessante Attribute mit Laufzeitunterstützung. Hier werden kurze Informationen zu verschiedenen Attributen gegeben, deren Existenz nur wenige Menschen kennen und noch weniger diejenigen, die sie verwenden. Da dies absolut unpraktische Informationen sind, wird es nicht viel Schimpfen geben (entgegen meiner Leidenschaft für nicht anwendbares Wissen)
  3. Einige der wenig bekannten Attribute, die nützlich sind, um sie zu kennen.
  4. Definieren Sie Ihr Attribut und verarbeiten Sie es. Hinzufügen von Attributen zur Laufzeit

Einführung


Beginnen Sie wie immer mit Definitionen und Spezifikationen. Dies wird dazu beitragen, die Attribute auf allen Ebenen zu verstehen und zu realisieren, was wiederum sehr nützlich ist, um die richtigen Anwendungen für sie zu finden.

Definieren Sie zunächst Metadaten. Metadaten sind Daten, die von CTS definierte Typen beschreiben und auf diese verweisen. Metadaten werden unabhängig von einer bestimmten Programmiersprache gespeichert. Daher bieten Metadaten einen allgemeinen Mechanismus für den Austausch von Informationen über ein Programm zur Verwendung zwischen Tools, die dies erfordern (Compiler und Debugger sowie das Programm selbst) sowie zwischen VES . Metadaten sind im Assemblymanifest enthalten. Sie können in einer PE- Datei zusammen mit IL- Code oder in einer separaten PE-Datei gespeichert werden, in der nur ein Assembly-Manifest vorhanden ist.
Ein Attribut ist ein Merkmal eines Typs oder seiner Mitglieder (oder anderer Sprachkonstrukte), das beschreibende Informationen enthält. Obwohl die häufigsten Attribute vordefiniert sind und ein bestimmtes Format in den Metadaten haben, können den Metadaten auch benutzerdefinierte Attribute hinzugefügt werden. Attribute sind kommutativ, d.h. Die Reihenfolge ihrer Deklaration über das Element ist unwichtig

Aus syntaktischer Sicht (in Metadaten) gibt es die folgenden Attribute

  1. Verwendung einer speziellen Syntax in IL. Schlüsselwörter sind beispielsweise Attribute. Und für sie gibt es eine spezielle Syntax in IL. Es gibt ziemlich viele von ihnen, alles aufzulisten macht keinen Sinn
  2. Verwendung der verallgemeinerten Syntax. Dazu gehören Benutzer- und Bibliotheksattribute.
  3. Sicherheitsattribute. Dazu gehören Attribute, die (direkt oder indirekt) von SecurityAttribute erben. Sie werden auf besondere Weise verarbeitet. In IL gibt es eine spezielle Syntax für sie, mit der Sie XML erstellen können, das diese Attribute direkt beschreibt

Beispiel


C # -Code, der alle oben genannten Attributtypen enthält
[StructLayout(LayoutKind.Explicit)] [Serializable] [Obsolete] [SecurityPermission(SecurityAction.Assert)] public class Sample { } 


Resultierende IL
 .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*/} } 


Wie Sie sehen können, hat StructLayoutAttribute eine spezielle Syntax, da es in IL als "explizit" dargestellt wird. ObsoleteAttribute verwendet eine gemeinsame Syntax - in IL beginnt mit ".custom". SecurityPermissionAttribute als Sicherheitsattribut wurde zu einem ".permissionset assert".

Benutzerattribute fügen Metadaten Benutzerinformationen hinzu. Dieser Mechanismus kann verwendet werden, um anwendungsspezifische Informationen zur Kompilierungszeit zu speichern und zur Laufzeit darauf zuzugreifen oder um sie von einem anderen Tool zu lesen und zu analysieren. Obwohl jeder benutzerdefinierte Typ als Attribut verwendet werden kann, erfordert die CLS- Konformität, dass die Attribute von System.Attribute erben. Die CLI definiert einige Attribute vor und verwendet sie zur Steuerung des Laufzeitverhaltens. Einige Sprachen definieren Attribute zur Darstellung von Sprachfunktionen, die nicht direkt in CTS dargestellt werden.

Wie bereits erwähnt, werden Attribute in Metadaten gespeichert, die wiederum in der Kompilierungsphase generiert werden, d. H. in die PE-Datei eingegeben (normalerweise * .dll). Daher können Sie ein Attribut zur Laufzeit nur hinzufügen, indem Sie die ausführbare Datei zur Laufzeit ändern (aber die Zeiten sich selbst ändernder Programme sind längst vorbei). Daraus folgt, dass sie in der Ausführungsphase nicht hinzugefügt werden können, dies ist jedoch nicht ganz korrekt. Wenn wir unsere Assembly bilden, Typen darin definieren, können wir in der Ausführungsphase einen neuen Typ erstellen und Attribute daran hängen. Formal können wir also zur Laufzeit noch Attribute hinzufügen (das Beispiel befindet sich ganz unten).

Nun ein wenig zu den Einschränkungen


Wenn sich aus irgendeinem Grund zwei Attribute in derselben Assembly mit den Namen Name und NameAtribute befinden, ist es unmöglich, das erste davon zu setzen. Bei Verwendung von [Name] (dh ohne Suffix) sieht der Compiler Unsicherheit. Wenn Sie [NameAttribute] verwenden, setzen wir NameAttribute, was logisch ist. Es gibt eine spezielle Syntax für solch eine mystische Situation mit mangelnder Vorstellungskraft beim Benennen. Um die erste Version ohne Suffix zu platzieren, können Sie das Vorzeichen des Hundes (dh [Name] ist ein Witz, es ist nicht erforderlich) vor dem Attributnamen [@Name] angeben.

Benutzerdefinierte Attribute können zu allen anderen als benutzerdefinierten Attributen hinzugefügt werden. Dies bezieht sich auf Metadaten, d.h. Wenn wir ein Attribut in C # über der Attributklasse platzieren, verweist es in Metadaten auf die Klasse. Sie können "public" jedoch kein Attribut hinzufügen. Sie können jedoch mit Assemblys, Modulen, Klassen, Werttypen, Aufzählungen, Konstruktoren, Methoden, Eigenschaften, Feldern, Ereignissen, Schnittstellen, Parametern, Delegaten, Rückgabewerten oder verallgemeinerten Parametern arbeiten. Das folgende Beispiel zeigt offensichtliche und nicht sehr gute Beispiele dafür, wie Sie einer bestimmten Konstruktion ein Attribut zuweisen können.

Syntax der Attributdeklaration
 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; } } } 


Attribute haben zwei Arten von Parametern - benannt und positionell. Positionsparameter umfassen Konstruktorparameter. Zu benannten öffentlichen Eigenschaften mit einem zugänglichen Setter. Darüber hinaus sind dies nicht nur formale Namen, sondern alle Parameter können angegeben werden, wenn ein Attribut in Klammern nach seinem Namen deklariert wird. Benannte sind optional.

Arten von Parametern
 [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 { } 


Gültige Parameter (beider Typen) für das Attribut müssen einer der folgenden Typen sein:

  1. bool, byte, char, double, float, int, long, short, string und weiter primitiv, außer dezimal
  2. Objekt
  3. System.Type
  4. Aufzählung
  5. Ein eindimensionales Array eines der oben genannten Typen

Dies liegt hauptsächlich an der Tatsache, dass es sich um eine Konstante zur Kompilierungszeit handeln sollte, und die oben genannten Typen können diese Konstante akzeptieren (indem wir ein Objekt akzeptieren, das wir int übergeben können). Aus irgendeinem Grund kann das Argument jedoch nicht vom Typ ValueType sein, obwohl dies aus logischer Sicht möglich ist.

Es gibt zwei Arten von Benutzerattributen: echte benutzerdefinierte Attribute und Pseudo-benutzerdefinierte .
Im Code sehen sie gleich aus (sie sind in eckigen Klammern über der Sprachstruktur angegeben), werden jedoch unterschiedlich verarbeitet:

  1. Das ursprüngliche Benutzerattribut wird direkt in den Metadaten gespeichert. Attributparameter werden unverändert gespeichert. Sie sind zur Laufzeit verfügbar und werden als Satz von Bytes gespeichert (ich möchte Sie schnell daran erinnern, dass sie zur Kompilierungszeit bekannt sind).
  2. Ein Pseudo-Benutzerattribut wird erkannt, weil sein Name zu einer speziellen Liste gehört. Anstatt seine Daten direkt in den Metadaten zu speichern, werden sie analysiert und zum Setzen von Bits oder Feldern in den Metadatentabellen verwendet. Die Daten werden dann verworfen und können nicht weiter empfangen werden. Metadatentabellen werden zur Laufzeit schneller überprüft als echte Benutzerattribute, und zum Speichern von Informationen ist weniger Speicher erforderlich.

Pseudo-Benutzerattribute sind keine sichtbare Reflexion
 [Serializable] [StructLayout(LayoutKind.Explicit)] public class CustomPseudoCustom { } class Program { static void Main() { var onlyCustom = typeof(CustomPseudoCustom).GetCustomAttributes(); // SerializableAttribute } } 


Die meisten Benutzerattribute werden auf Sprachebene eingeführt. Sie werden von der Laufzeit gespeichert und zurückgegeben, während die Laufzeit nichts über die Bedeutung dieser Attribute weiß. Alle Pseudo-Benutzerattribute sowie einige Benutzerattribute sind jedoch für Compiler und die CLI von besonderem Interesse. Also fahren wir mit dem nächsten Abschnitt fort.

Laufzeitfähige Attribute


Dieser Abschnitt ist rein informativ. Wenn kein Interesse an der Verwendung der Laufzeit besteht, können Sie zum nächsten Abschnitt blättern.

In der folgenden Tabelle sind Pseudo-Benutzerattribute und spezielle Benutzerattribute aufgeführt (CLIs oder Compiler behandeln sie auf besondere Weise).

Pseudo-Benutzerattribute (sie können nicht durch Reflexion erhalten werden).
CLI-Attribute:
AttributBeschreibung
AssemblyAlgorithmIDAttributeSchreibt die Kennung des verwendeten Hash-Algorithmus. Legt das Feld Assembly.HashAlgId fest
AssemblyFlagsAttributeSchreibt Flags für die entsprechende Assembly. Legt das Feld Assembly.Flags fest
DllImportAttributeBietet Informationen zu Code, der in einer nicht verwalteten Bibliothek implementiert ist. Setzt das Method.Flags.PinvokeImpl-Bit der entsprechenden Methode. fügt ImplMap einen neuen Eintrag hinzu (indem die Werte von MappingFlags, MemberForwarded, ImportName und ImportScope festgelegt werden)
StructLayoutAttributeErmöglicht das explizite Festlegen der Methode zum Platzieren von Referenzfeldern oder signifikanten Typen. Legt das Feld TypeDef.Flags.LayoutMask für den Typ fest. Es können auch die Felder TypeDef.Flags.StringFormatMask, ClassLayout.PackingSize und ClassLayout.ClassSize festgelegt werden
FieldOffsetAttributeDefiniert den Byte-Offset von Feldern in einer Referenz oder einem signifikanten Typ. Legt den Wert von FieldLayout.OffSet für die entsprechende Methode fest.
InattributeGibt an, dass der Parameter als [in] -Argument übergeben wird. Setzt das Param.Flags.In-Bit für den entsprechenden Parameter.
OutattributeGibt an, dass der Parameter als [out] -Argument übergeben wird. Setzt das Param.Flags.Out-Bit für den entsprechenden Parameter.
MarschallattributDefiniert, wie Daten zwischen verwaltetem und nicht verwaltetem Code gemarshallt werden. Setzt das Field.Flags.HasFieldMarshal-Bit für das Feld (oder das Param.Flags.HasFieldMarshal-Bit für den Parameter). Fügt der FieldMarshal-Tabelle einen Eintrag hinzu (indem Sie die Werte von Parent und NativeType festlegen).
MethodImplAttributeDefiniert Implementierungsdetails für eine Methode. Legt den Wert von Method.ImplFlags für die entsprechende Methode fest


CLS-Attribute - Sprachen müssen sie unterstützen:
AttributBeschreibung
AttributeUsageAttributeWird verwendet, um anzugeben, wie ein Attribut verwendet werden kann.
Veraltetes AttributGibt an, dass das Element nicht verwendet werden soll.
CLSCompliantAttributeGibt an, ob ein Element als CLS-kompatibel deklariert ist.

Verschiedenes interessant
AttributBeschreibung
ThreadStaticAttributeStellt Stream-bezogene Typfelder bereit
ConditionalAttributeMarkiert die Methode basierend auf einer Kompilierungsbedingung (angegeben in / define) als aufgerufen. Wenn die Bedingung nicht erfüllt ist, wird die Methode nicht aufgerufen (und nicht in IL kompiliert). Nur die void-Methode kann markiert werden. Andernfalls tritt ein Kompilierungsfehler auf.
DecimalConstantAttributeSpeichert den dezimalen Konstantenwert in Metadaten
DefaultMemberAttributeDefiniert das Mitglied der Klasse, das standardmäßig mit der InvokeMember-Methode verwendet werden soll.
CompilationRelaxationsAttributeGibt an, ob Ausnahmen von Anweisungsprüfungen streng oder gelockert sind. Derzeit können Sie nur den Parameter NoStringInterning übergeben, der die Assembly als nicht interning für Zeichenfolgenliterale kennzeichnend markiert. Dieser Mechanismus kann jedoch weiterhin verwendet werden.
FlagsAttributeAttribut, das angibt, ob Enum als Bit-Flags behandelt werden soll
IndexerNameAttributeGibt den Namen an, unter dem der Indexer in Programmiersprachen bekannt ist, die diese Funktion nicht direkt unterstützen.
ParamArrayAttributeGibt an, dass die Methode eine variable Anzahl von Parametern akzeptiert.

Nützliche Attribute


Ein wesentlicher Bestandteil der Softwareproduktentwicklung ist das Debuggen. In einem großen und komplexen System dauert es oft Dutzende und Hunderte Male, dieselbe Methode auszuführen und den Status von Objekten zu überwachen. Gleichzeitig beginnt es bereits zu einem Zeitpunkt von 20, die Notwendigkeit, ein Objekt 400-mal tief zu erweitern, um den Wert einer Variablen zu sehen und die Methode erneut zu starten, besonders zu verärgern.
Für ein leiseres und schnelleres Debuggen können Sie Attribute verwenden, die das Verhalten des Debuggers ändern.

DebuggerDisplayAttribute gibt an, wie der Typ oder sein Mitglied im Fenster der Debugger-Variablen angezeigt wird (und nicht nur).

Das einzige Argument für den Konstruktor ist eine Zeichenfolge mit einem Anzeigeformat. Was zwischen den Klammern sein wird, wird berechnet. Das Format ist wie eine interpolierte Zeichenfolge, nur ohne Dollar. Sie können keine Zeiger in einem berechneten Wert verwenden. Übrigens, wenn Sie einen überschriebenen ToString haben, wird sein Wert so angezeigt, als ob er in diesem Attribut wäre. Wenn sowohl ein ToString als auch ein Attribut vorhanden sind, wird der Wert dem Attribut entnommen.


DebuggerBrowsableAttribute definiert, wie ein Feld oder eine Eigenschaft im Fenster mit den Debugger-Variablen angezeigt wird. Akzeptiert einen DebuggerBrowsableState mit drei Optionen:

  • Nie - das Feld wird beim Debuggen überhaupt nicht angezeigt. Beim Erweitern der Objekthierarchie wird dieses Feld nicht angezeigt
  • Reduziert - Das Feld ist ungelöst, kann aber erweitert werden. Dies ist das Standardverhalten.
  • RootHidden - Das Feld selbst wird nicht angezeigt, aber die Objekte, aus denen es besteht, werden angezeigt (für Arrays und Sammlungen).



DebuggerTypeProxy - Wenn ein Objekt hunderte Male am Tag im Debugger angezeigt wird, können Sie verwirrt sein und 3 Minuten damit verbringen, ein Proxy-Objekt zu erstellen, das das Quellobjekt so anzeigt, wie es sollte. In der Regel ist das anzuzeigende Proxy-Objekt die innere Klasse. Tatsächlich wird es anstelle des Zielobjekts angezeigt.



Andere nützliche Attribute

ThreadStatic - Ein Attribut, mit dem Sie eine statische Variable für jeden Thread zu einer eigenen machen können. Setzen Sie dazu das Attribut über das statische Feld. Es lohnt sich, sich an eine wichtige Nuance zu erinnern: Die Initialisierung durch einen statischen Konstruktor wird nur einmal durchgeführt, und die Variable ändert sich in dem Thread, den der statische Konstruktor ausführt. Im übrigen bleibt es voreingestellt. (PS. Wenn Sie dieses Verhalten benötigen, empfehle ich Ihnen, sich der ThreadLocal-Klasse zuzuwenden.)

Ein wenig über die Nuancen des Motorraums. Sowohl unter Linux als auch unter Windows gibt es einen lokalen Speicherbereich für den Stream ( TLS bzw. TSD ). Diese Bereiche selbst sind jedoch sehr klein. Daher wird eine ThreadLocalInfo-Struktur erstellt, auf die in TLS ein Zeiger gesetzt wird. Dementsprechend wird nur ein Steckplatz verwendet. Die Struktur selbst enthält 3 Felder - Thread, AppDomain, ClrTlsInfo. Wir interessieren uns für die erste. Es ist es, das die Speicherung der Flussstatik im Speicher mithilfe von ThreadLocalBlock und ThreadLocalModule organisiert.

Auf diese Weise:

  • Referenztypen - ThreadStaticHandleTable befindet sich auf dem Heap und wird von der ThreadLocalBlock-Klasse unterstützt.
  • Strukturen - In einem verwalteten Heap gepackt und gespeichert sowie Referenztypen
  • Primitive signifikante Typen werden in Bereichen des nicht verwalteten Speichers gespeichert, die Teil von ThreadLocalModule sind

Nun, da wir darüber sprechen, ist es erwähnenswert, über asynchrone Methoden zu sprechen. Wie ein aufmerksamer Leser vielleicht bemerkt, wird bei Verwendung der Asynchronität die Fortsetzung nicht unbedingt im selben Thread ausgeführt (wir können den Ausführungskontext beeinflussen, jedoch nicht den Thread). Dementsprechend bekommen wir einen Mist, wenn wir ThreadLocal verwenden. In diesem Fall wird empfohlen, AsyncLocal zu verwenden. Aber der Artikel handelt nicht davon, also gingen wir weiter.

InternalsVisibleTo - Mit dieser Option können Sie die Baugruppe angeben, die für als intern gekennzeichnete Elemente sichtbar ist. Es scheint, dass Sie, wenn eine Versammlung bestimmte Typen und deren Mitglieder benötigt, diese einfach als öffentlich und nicht als Dampf markieren können. Eine gute Architektur setzt jedoch voraus, dass Implementierungsdetails ausgeblendet werden. Trotzdem können sie für einige Infrastrukturzwecke benötigt werden, beispielsweise für Testprojekte. Mit diesem Attribut können Sie sowohl die Kapselung als auch den erforderlichen Prozentsatz der Testabdeckung unterstützen.

HandleProcessCorruptedStateExceptions - Ermöglicht es Ihnen, schüchterne Programmierer zu erschrecken und Ausnahmen eines beschädigten Zustands abzufangen . Für solche Ausnahmen wird die CLR standardmäßig nicht abgefangen. Im Allgemeinen wäre die beste Lösung, die Anwendung abstürzen zu lassen. Dies sind gefährliche Ausnahmen, die darauf hinweisen, dass der Prozessspeicher beschädigt ist. Daher ist die Verwendung dieses Attributs eine sehr schlechte Idee. In einigen Fällen ist es jedoch möglich, dass für die lokale Entwicklung dieses Attribut für eine Weile festgelegt wird. Um die Ausnahme eines beschädigten Zustands abzufangen, setzen Sie dieses Attribut einfach über die Methode. Und wenn dieses Attribut bereits verwendet wird, wird (wie immer) empfohlen, eine bestimmte Ausnahme abzufangen.

DisablePrivateReflection - macht alle privaten Mitglieder der Assembly für die Reflexion unerreichbar. Das Attribut wird in die Assembly eingefügt.

Definieren Sie Ihr Attribut


Nicht nur, weil dieser Abschnitt der letzte ist. Der beste Weg, um zu verstehen, in welchen Fällen die Verwendung des Attributs von Vorteil ist, besteht darin, sich bereits verwendete Attribute anzusehen. Es ist schwierig, eine formalisierte Regel zu formulieren, wenn Sie über Ihr eigenes Attribut nachdenken sollten. Oft werden sie als zusätzliche Informationen über einen Typ / sein Mitglied oder ein anderes Sprachkonstrukt verwendet, das völlig anderen Entitäten gemeinsam ist. Als Beispiel alle Attribute, die für Serialisierung / ORM / Formatierung usw. verwendet werden. Aufgrund der umfassenden Anwendung dieser Mechanismen auf völlig unterschiedliche Typen, die den Entwicklern des entsprechenden Mechanismus häufig nicht bekannt sind, ist die Verwendung von Attributen eine hervorragende Möglichkeit, dem Benutzer die Bereitstellung deklarativer Informationen für diesen Mechanismus zu ermöglichen.

Die Verwendung Ihrer Attribute kann in zwei Teile unterteilt werden:

  1. Ein Attribut erstellen und verwenden
  2. Ein Attribut abrufen und verarbeiten

Ein Attribut erstellen und verwenden


Um Ihr Attribut zu erstellen, reicht es aus, von System.Attribute zu erben. In diesem Fall ist es ratsam, den genannten Namensstil einzuhalten - den Klassennamen auf Attribut zu beenden. Es wird jedoch kein Fehler angezeigt, wenn Sie dieses Suffix weglassen. Wie bereits erwähnt, können Attribute zwei Arten von Parametern haben - positionell und benannt. Die Logik ihrer Anwendung ist dieselbe wie bei den Eigenschaften und Parametern des Konstruktors für die Klasse - die Werte, die zum Erstellen des Objekts erforderlich sind, für das es keinen vernünftigen "Standard" gibt, werden in Position (dh Konstruktor) gestellt. Was vernünftigerweise voreingestellt werden kann, was häufig verwendet wird, wird besser in eine benannte (d. H. Eine Eigenschaft) unterschieden.

Von nicht geringer Bedeutung für die Erstellung eines Attributs ist die Begrenzung seiner Anwendungsorte. Hierfür wird AttributeUsageAttribute verwendet. Der erforderliche Parameter (Position) ist das AttributeTarget, das bestimmt, wo das Attribut verwendet wird (Methode, Assembly usw.). Optionale (benannte) Parameter sind:

  1. AllowMultiple - Gibt an, ob es möglich ist, mehr als ein Attribut über dem Ort seiner Anwendung zu platzieren oder nicht. Standardmäßig falsch
  2. Geerbt - Bestimmt, ob dieses Attribut zu Klassenvererbern (bei Platzierung über der Basisklasse) und überschriebenen Methoden (bei Platzierung über der Methode) gehört. Der Standardwert ist true.

Danach können Sie die Attribute mit einer Nutzlast laden. Ein Attribut ist eine deklarative Information, dh alles, was darin definiert ist, sollte die Konstruktion beschreiben, auf die es sich bezieht. Das Attribut sollte keine tiefe Logik enthalten. Für die Verarbeitung der von Ihnen definierten Attribute sollten spezielle Services verantwortlich sein, die sie nur verarbeiten. Die Tatsache, dass das Attribut keine Logik haben sollte, bedeutet jedoch nicht, dass es keine Methoden haben sollte.

Eine Methode (Funktion) ist auch Information und kann auch ein Design beschreiben. Mithilfe von Polymorphismus in Attributen können Sie ein sehr leistungsfähiges und praktisches Tool bereitstellen, mit dem der Benutzer sowohl die von Ihrem Tool verwendeten Informationen als auch bestimmte Ausführungs- und Verarbeitungsstufen beeinflussen kann.In diesem Fall muss er keine Klassen erstellen, Abhängigkeiten, Kostenfabriken und deren Schnittstellen einfügen, die diese Klassen erstellen. Es reicht aus, eine einzelne Erbenklasse zu erstellen, die die Details der Arbeit mit dem Element enthält, auf das sie sich bezieht. In der Regel reicht jedoch das übliche ROSO-Attribut mit einigen Eigenschaften aus.

Abrufen und Verarbeiten eines Attributs


Die Verarbeitung der empfangenen Attribute hängt vom jeweiligen Fall ab und kann auf völlig unterschiedliche Weise erfolgen. Es ist schwierig, hierfür nützliche Funktionen und Tricks anzugeben.

Attribute werden zur Laufzeit durch Reflektion erhalten. Es gibt verschiedene Möglichkeiten, ein Attribut von einem bestimmten Element abzurufen.

Aber alles stammt von der ICustomAttributeProvider- Oberfläche . Es wird von Typen wie Assembly, MemberInfo, Module, ParameterInfo implementiert. Die Nachfolger von MemberInfo sind wiederum Type, EventInfo, FieldInfo, MethodBase, PropertyInfo.

Die Schnittstelle hat nur 3 Funktionen und sie sind nicht sehr praktisch. Sie arbeiten mit Arrays (auch wenn wir wissen, dass es nur ein Attribut geben kann) und sind nicht nach Typ parametrisiert (sie verwenden Objekt). Daher müssen Sie selten direkt auf die Funktionen dieser Schnittstelle zugreifen (ich habe es nie gesagt, weil ich nicht kategorisch sein möchte). Zur Vereinfachung der Verwendung gibt es eine CustomAttributeExtensions- Klasse , in der es viele Erweiterungsmethoden für alle Arten von Typen gibt, die einfache Operationen zum Umwandeln, Auswählen eines einzelnen Werts usw. ausführen, wodurch der Entwickler von dieser Notwendigkeit befreit wird. Diese Methoden sind auch als statisch in der Attributklasse verfügbar, mit der nützlichsten Funktion, den geerbten Parameter zu ignorieren (für Nonkonformisten).

Die wichtigsten verwendeten Funktionen sind unten aufgeführt. Der erste Parameter, der angibt, welcher Typ die Methode erweitert, habe ich weggelassen. Überall dort, wo der bool-Vererbungsparameter angegeben ist , kommt es ohne ihn zu einer Überladung (mit dem Standardwert true ). Dieser Parameter gibt an, ob die Attribute der übergeordneten Klasse oder der Basismethode bei der Ausführung der Methode berücksichtigt werden sollen (falls sie für eine überschriebene Methode verwendet werden). Wenn im Attribut erben = flase angegeben ist , hilft es nicht, die Attribute der Basisklasse zu berücksichtigen, wenn Sie es auf true setzen
MethodennameBeschreibung
GetCustomAttributes <LogAttribute> (Bool-Erbe)Ruft eine Aufzählung von Attributen des angegebenen Typs ab. Wenn das Attribut eins ist, wird eine Aufzählung von 1 Element zurückgegeben
GetCustomAttribute <LogAttribute> (Bool-Erbe)Gibt ein einzelnes Attribut des angegebenen Typs zurück. Wenn mehrere vorhanden sind, lösen Sie eine System.Reflection.AmbiguousMatchException aus: Mehrere benutzerdefinierte Attribute desselben Typs haben eine Ausnahme gefunden
GetCustomAttributes ()Gibt eine Aufzählung von Attributen aller Typen zurück
GetCustomAttributesData ()Gibt eine Aufzählung CustomAttributeData zurück, in der es Eigenschaften gibt, mit denen Sie einen Konstruktor, Parameter (benannt und positionell) und Konstruktorargumente abrufen können
IsDefined (Typ attrType, bool erben)Gibt true zurück, wenn das Attribut über dem Element deklariert ist, andernfalls false

Aus Gründen der Klarheit schlage ich vor, eine kleine Demo der Arbeit aller genannten Funktionen anzusehen.

Rückgabewert der oben genannten Methoden
 [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 } } 


Aus akademischen Gründen gebe ich ein Beispiel für die Definition von Attributen zur Laufzeit. Dieser Code erhebt nicht den Anspruch, der schönste und am meisten unterstützte zu sein.

Code
 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/de468287/


All Articles