Atributos C #: sobre todos os aspectos

Olá leitor. Este artigo descreve atributos de todos os lados - começando pela especificação, significado e definição de atributos, criando os seus próprios e trabalhando com eles, terminando com a adição de atributos em tempo de execução e os atributos existentes mais úteis e interessantes. Se você estiver interessado no tópico de atributos em C #, seja bem-vindo ao gato.


Conteúdo


  1. 1. Introdução Definindo e atribuindo atributos
  2. Atributos interessantes com suporte a tempo de execução. Aqui, serão fornecidas informações breves sobre vários atributos, cuja existência poucas pessoas sabem e muito menos quem as usa. Como essa é uma informação absolutamente impraticável, não haverá muita reclamação (ao contrário da minha paixão pelo conhecimento inaplicável)
  3. Alguns dos atributos pouco conhecidos que são úteis para conhecer.
  4. Definindo seu atributo e processando-o. Adicionando atributos no tempo de execução

1. Introdução


Como sempre, comece com definições e especificações. Isso ajudará a entender e perceber os atributos em todos os níveis, o que, por sua vez, é muito útil para encontrar os aplicativos certos para eles.

Comece definindo metadados. Metadados são dados que descrevem e se referem aos tipos definidos pelo CTS . Os metadados são armazenados de maneira independente de qualquer linguagem de programação específica. Assim, os metadados fornecem um mecanismo geral para a troca de informações sobre um programa para uso entre as ferramentas que o exigem (compiladores e depuradores, bem como o próprio programa), bem como entre o VES . Os metadados estão incluídos no manifesto do assembly. Eles podem ser armazenados em um arquivo PE junto com o código IL ou em um arquivo PE separado, onde haverá apenas um manifesto de assembly.
Um atributo é uma característica de um tipo ou de seus membros (ou outras construções de linguagem) que contém informações descritivas. Embora os atributos mais comuns sejam predefinidos e tenham um formato específico nos metadados, os atributos personalizados também podem ser adicionados aos metadados. Os atributos são comutativos, ou seja, a ordem de sua declaração sobre o elemento não é importante

Do ponto de vista sintático (em metadados), existem os seguintes atributos

  1. Usando sintaxe especial em IL. Por exemplo, palavras-chave são atributos. E para eles existe uma sintaxe especial em IL. Existem muitos deles; listar tudo não faz sentido
  2. Usando sintaxe generalizada. Isso inclui atributos de usuário e biblioteca.
  3. Atributos de segurança. Isso inclui atributos que herdam de SecurityAttribute (direta ou indiretamente). Eles são processados ​​de uma maneira especial. Existe uma sintaxe especial para eles na IL, que permite criar xml que descreve esses atributos diretamente

Exemplo


Código C # que contém todos os tipos de atributos acima
[StructLayout(LayoutKind.Explicit)] [Serializable] [Obsolete] [SecurityPermission(SecurityAction.Assert)] public class Sample { } 


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


Como você pode ver, o StructLayoutAttribute possui uma sintaxe especial, pois em IL é representado como "explícito". ObsoleteAttribute usa uma sintaxe comum - em IL começa com ".custom". SecurityPermissionAttribute como um atributo de segurança se transformou em ".permissionset assert".

Os atributos do usuário adicionam informações do usuário aos metadados. Esse mecanismo pode ser usado para armazenar informações específicas de aplicativos em tempo de compilação e para acessá-las em tempo de execução ou para leitura e análise por outra ferramenta. Embora qualquer tipo definido pelo usuário possa ser usado como um atributo, a conformidade com o CLS exige que os atributos sejam herdados de System.Attribute. A CLI pré- define alguns atributos e os utiliza para controlar o comportamento do tempo de execução. Alguns idiomas definem atributos para representar recursos de idioma não representados diretamente no CTS.

Como já mencionado, os atributos são armazenados em metadados, que, por sua vez, são gerados no estágio de compilação, ou seja, inserido no arquivo PE (geralmente * .dll). Portanto, você pode adicionar um atributo no tempo de execução apenas modificando o arquivo executável no tempo de execução (mas os tempos dos programas de alteração automática se esgotaram há muito tempo). Segue-se que eles não podem ser adicionados no estágio de execução, mas isso não é totalmente exato. Se formarmos nossa montagem, definirmos tipos nela, poderemos criar um novo tipo no estágio de execução e travar atributos nele. Então, formalmente, ainda podemos adicionar atributos em tempo de execução (o exemplo estará na parte inferior).

Agora um pouco sobre as limitações


Se, por algum motivo, houver 2 atributos no mesmo assembly com os nomes Name e NameAtribute, torna-se impossível colocar o primeiro deles. Ao usar [Nome] (ou seja, sem sufixo), o compilador diz que vê incerteza. Ao usar [NameAttribute], colocaremos NameAttribute, o que é lógico. Existe uma sintaxe especial para uma situação tão mística com falta de imaginação ao nomear. Para colocar a primeira versão sem sufixo, você pode especificar o sinal do cachorro (ou seja, [Nome] é uma piada, não é necessário) antes do nome do atributo [@Name].

Atributos personalizados podem ser adicionados a qualquer coisa, exceto atributos personalizados. Refere-se a metadados, ou seja, se colocarmos um atributo em C # acima da classe de atributo, nos metadados ele se referirá à classe. Mas você não pode adicionar um atributo a "public". Mas você pode fazer com montagens, módulos, classes, tipos de valor, enumerações, construtores, métodos, propriedades, campos, eventos, interfaces, parâmetros, delegados, valores de retorno ou parâmetros generalizados. O exemplo abaixo mostra exemplos óbvios e não muito claros de como você pode colocar um atributo em uma construção específica.

Sintaxe da Declaração de Atributo
 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; } } } 


Os atributos têm 2 tipos de parâmetros - nomeados e posicionais. Parâmetros posicionais incluem parâmetros do construtor. Para nomeado - propriedades públicas com um configurador acessível. Além disso, esses não são apenas nomes formais; todos os parâmetros podem ser indicados ao declarar um atributo entre colchetes após seu nome. Os nomeados são opcionais.

Tipos de Parâmetros
 [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 { } 


Os parâmetros válidos (de ambos os tipos) para o atributo devem ser um dos seguintes tipos:

  1. bool, byte, char, double, float, int, long, short, string e mais no primitivo, exceto decimal
  2. objeto
  3. System.Type
  4. enum
  5. Uma matriz unidimensional de qualquer um dos tipos acima

Isso se deve em grande parte ao fato de que deve ser uma constante em tempo de compilação, e os tipos acima podem aceitar essa constante (aceitando um objeto que podemos passar int). Mas, por alguma razão, o argumento não pode ser do tipo ValueType, embora isso seja possível do ponto de vista lógico.

Existem dois tipos de atributos do usuário: atributos customizados genuínos e pseudo-customizados .
No código, eles têm a mesma aparência (são indicados acima da estrutura do idioma entre colchetes), mas são processados ​​de maneira diferente:

  1. O atributo do usuário original é armazenado diretamente nos metadados; parâmetros de atributo são armazenados como estão. Eles estão disponíveis em tempo de execução e são salvos como um conjunto de bytes (eu me apresso para lembrá-lo de que eles são conhecidos em tempo de compilação)
  2. Um atributo de pseudo-usuário é reconhecido porque seu nome faz parte de uma lista especial. Em vez de armazenar seus dados diretamente nos metadados, eles são analisados ​​e usados ​​para definir bits ou campos nas tabelas de metadados, e os dados são descartados e não podem mais ser recebidos. As tabelas de metadados são verificadas em tempo de execução mais rapidamente que os atributos genuínos do usuário e é necessário menos armazenamento para armazenar informações.

Atributos de pseudo-usuário não são reflexo visível
 [Serializable] [StructLayout(LayoutKind.Explicit)] public class CustomPseudoCustom { } class Program { static void Main() { var onlyCustom = typeof(CustomPseudoCustom).GetCustomAttributes(); // SerializableAttribute } } 


A maioria dos atributos do usuário são introduzidos no nível do idioma. Eles são armazenados e retornados pelo tempo de execução, enquanto o tempo de execução não sabe nada sobre o significado desses atributos. Mas todos os atributos de pseudo-usuário mais alguns atributos de usuário são de particular interesse para os compiladores e para a CLI. Então, vamos para a próxima seção.

Atributos ativados por tempo de execução


Esta seção é meramente informativa; se não houver interesse em usar o tempo de execução, você poderá rolar para a próxima seção.

A tabela abaixo lista atributos de pseudo-usuário e atributos especiais de usuário (CLIs ou compiladores os tratam de uma maneira especial).

Atributos de pseudo-usuário (eles não podem ser obtidos através da reflexão).
Atributos da CLI:
AtributoDescrição do produto
AssemblyAlgorithmIDAttributeGrava o identificador do algoritmo de hash usado. Define o campo Assembly.HashAlgId
AssemblyFlagsAttributeGrava sinalizadores para a montagem correspondente. Define o campo Assembly.Flags
DllImportAttributeFornece informações sobre o código implementado em uma biblioteca não gerenciada. Define o bit Method.Flags.PinvokeImpl do método correspondente; adiciona uma nova entrada ao ImplMap (configurando os valores de MappingFlags, MemberForwarded, ImportName e ImportScope)
StructLayoutAttributePermite definir explicitamente o método para colocar campos de referência ou tipo significativo. Define o campo TypeDef.Flags.LayoutMask para o tipo. Também pode definir os campos TypeDef.Flags.StringFormatMask, ClassLayout.PackingSize e ClassLayout.ClassSize
FieldOffsetAttributeDefine o deslocamento de byte dos campos em uma referência ou tipo significativo. Define o valor de FieldLayout.OffSet para o método correspondente.
AtribuirIndica que o parâmetro é passado como um argumento [in]. Define o bit Param.Flags.In para o parâmetro correspondente.
AtribuirIndica que o parâmetro é passado como um argumento [out]. Define o bit Param.Flags.Out para o parâmetro correspondente.
MarshalasattributeDefine como os dados são empacotados entre código gerenciado e não gerenciado. Define o bit Field.Flags.HasFieldMarshal para o campo (ou o bit Param.Flags.HasFieldMarshal para o parâmetro); Adiciona uma entrada à tabela FieldMarshal (configurando os valores de Parent e NativeType)
MethodImplAttributeDefine detalhes de implementação para um método. Define o valor de Method.ImplFlags para o método correspondente


Atributos do CLS - Os idiomas devem suportá-los:
AtributoDescrição do produto
AttributeUsageAttributeUsado para indicar como um atributo pode ser usado.
ObsoleteAttributeIndica que o item não deve ser usado.
CLSCompliantAttributeIndica se um item é declarado como compatível com CLS.

Diversos interessantes
AtributoDescrição do produto
ThreadStaticAttributeFornece campos de tipo relacionados ao fluxo
ConditionalAttributeMarca o método como chamado com base em uma condição de compilação (especificada em / define). Se a condição não for atendida, o método não será chamado (e não será compilado no IL). Somente o método nulo pode ser marcado. Caso contrário, ocorrerá um erro de compilação.
DecimalConstantAttributeArmazena o valor constante decimal em metadados
DefaultMemberAttributeDefine o membro da classe a ser usado por padrão com o método InvokeMember.
CompilationRelaxationsAttributeIndica se as exceções às verificações de instruções são rigorosas ou relaxadas. Atualmente, você só pode passar o parâmetro NoStringInterning, que marca o assembly como não exigindo internação literal de cadeia de caracteres. Mas esse mecanismo ainda pode ser usado.
FlagsAttributeAtributo que indica se a enumeração deve ser tratada como sinalizadores de bit
IndexerNameAttributeEspecifica o nome pelo qual o indexador será conhecido nas linguagens de programação que não suportam diretamente esse recurso.
ParamArrayAttributeIndica que o método aceita um número variável de parâmetros.

Atributos úteis


Uma parte integrante do desenvolvimento de produtos de software é a depuração. E geralmente em um sistema grande e complexo, são necessárias dezenas e centenas de vezes para executar o mesmo método e monitorar o estado dos objetos. Ao mesmo tempo, aos 20 anos, ele já começa a enfurecer especificamente a necessidade de expandir um objeto profundamente 400 vezes para ver o valor de uma variável e reiniciar o método novamente.
Para uma depuração mais silenciosa e rápida, você pode usar atributos que modificam o comportamento do depurador.

DebuggerDisplayAttribute indica como o tipo ou seu membro é exibido na janela de variáveis ​​do depurador (e não apenas).

O único argumento para o construtor é uma sequência com um formato de exibição. O que será entre as chaves será calculado. O formato é como uma sequência interpolada, apenas sem um dólar. Você não pode usar ponteiros em um valor calculado. A propósito, se você tiver um ToString substituído, seu valor será mostrado como se estivesse neste atributo. Se houver um ToString e um atributo, o valor será obtido do atributo.


DebuggerBrowsableAttribute define como um campo ou propriedade é exibido na janela de variáveis ​​do depurador. Aceita um DebuggerBrowsableState, que possui 3 opções:

  • Nunca - o campo não é exibido durante a depuração. Ao expandir a hierarquia de objetos, este campo não será exibido
  • Recolhido - o campo não foi resolvido, mas pode ser expandido. Esse é o comportamento padrão.
  • RootHidden - o próprio campo não é mostrado, mas os objetos dos quais consiste são mostrados (para matrizes e coleções)



DebuggerTypeProxy - se o objeto for exibido no depurador centenas de vezes por dia, você poderá ficar confuso e gastar 3 minutos criando um objeto proxy que exibe o objeto original como deveria. Normalmente, o objeto proxy a ser exibido é a classe interna. Na verdade, ele será exibido em vez do objeto de destino.



Outros atributos úteis

ThreadStatic - um atributo que permite criar uma variável estática própria para cada thread. Para fazer isso, coloque o atributo acima do campo estático. Vale lembrar uma importante nuance - a inicialização por um construtor estático será executada apenas uma vez e a variável será alterada no encadeamento que o construtor estático executará. No restante, ele permanecerá no padrão. (PS. Se você precisar desse comportamento, aconselho a procurar a classe ThreadLocal).

Um pouco sobre as nuances do compartimento do motor. No Linux e no Windows, há uma área de memória local para o fluxo ( TLS e TSD, respectivamente). No entanto, essas áreas são muito pequenas. Portanto, uma estrutura ThreadLocalInfo é criada, um ponteiro para o qual é colocado no TLS. Por conseguinte, apenas um slot é usado. A estrutura em si contém 3 campos - Thread, AppDomain, ClrTlsInfo. Estamos interessados ​​no primeiro. É isso que organiza o armazenamento de estática de fluxo na memória, usando ThreadLocalBlock e ThreadLocalModule para isso.

Desta forma:

  • Tipos de referência - localizados no heap, ThreadStaticHandleTable, suportado pela classe ThreadLocalBlock, mantém links para eles.
  • Estruturas - Empacotadas e armazenadas em um heap gerenciado, bem como tipos de referência
  • Tipos significativos primitivos são armazenados em áreas de memória não gerenciada que fazem parte do ThreadLocalModule

Bem, já que estamos falando sobre isso, vale a pena mencionar sobre métodos assíncronos. Como um leitor atento pode perceber, se usarmos assincronia, a continuação não será necessariamente executada no mesmo encadeamento (podemos influenciar o contexto de execução, mas não o encadeamento). Conseqüentemente, temos uma porcaria se usarmos o ThreadLocal. Nesse caso, é recomendável usar o AsyncLocal. Mas o artigo não é sobre isso, então fomos mais longe.

InternalsVisibleTo - permite especificar a montagem, que ficará visível para os elementos marcados como internos . Pode parecer que, se alguma assembléia precisar de certos tipos e de seus membros, você pode simplesmente marcá-los em público e não em vapor. Mas uma boa arquitetura implica ocultar detalhes de implementação. No entanto, eles podem ser necessários para algumas coisas de infraestrutura, por exemplo, projetos de teste. Usando esse atributo, você pode suportar o encapsulamento e a porcentagem necessária de cobertura de teste.

HandleProcessCorruptedStateExceptions - permite assustar programadores tímidos e capturar exceções de um estado danificado. Por padrão, para essas exceções, o CLR não intercepta. Em geral, a melhor solução seria deixar o aplicativo travar. Essas são exceções perigosas que indicam que a memória do processo está corrompida, portanto, usar esse atributo é uma péssima idéia. Mas é possível em alguns casos, para o desenvolvimento local, será útil definir esse atributo por um tempo. Para capturar a exceção de um estado danificado, basta colocar esse atributo acima do método. E se ele já alcançou o uso desse atributo, é recomendável (no entanto, como sempre) capturar alguma exceção específica.

DisablePrivateReflection - torna todos os membros privados da montagem inatingíveis para reflexão. O atributo é colocado na montagem.

Definindo seu atributo


Não apenas porque esta seção é a última. Afinal, a melhor maneira de entender em quais casos será benéfico usar o atributo é examinar os já usados. É difícil dizer uma regra formalizada quando você deve pensar em seu próprio atributo. Geralmente, eles são usados ​​como informações adicionais sobre um tipo / seu membro ou outra construção de idioma comum a entidades completamente diferentes. Como exemplo, todos os atributos usados ​​para serialização / ORM / formatação etc. Devido à aplicação extensiva desses mecanismos a tipos completamente diferentes, geralmente desconhecidos pelos desenvolvedores do mecanismo correspondente, o uso de atributos é uma ótima maneira de permitir que o usuário forneça informações declarativas para esse mecanismo.

O uso de seus atributos pode ser dividido em 2 partes:

  1. Criando um atributo e usando-o
  2. Obtendo um atributo e processando-o

Criando um atributo e usando-o


Para criar seu atributo, basta herdar de System.Attribute . Nesse caso, é aconselhável aderir ao estilo de nomeação mencionado - encerre o nome da classe em Atributo. No entanto, não haverá erro se você omitir esse sufixo. Como mencionado anteriormente, os atributos podem ter 2 tipos de parâmetros - posicional e nomeado. A lógica de sua aplicação é a mesma das propriedades e parâmetros do construtor da classe - os valores necessários para criar o objeto para o qual não existe um "padrão" razoável são colocados em posição (construtor). O que pode ser razoavelmente padrão, que geralmente será usado, é melhor distinguido em um nomeado (ou seja, uma propriedade).

De pouca importância na criação de um atributo é a limitação de seus locais de aplicação. AttributeUsageAttribute é usado para isso. O parâmetro necessário (posicional) é o AttributeTarget, que determina onde o atributo é usado (método, montagem etc.). Parâmetros opcionais (nomeados) são:

  1. AllowMultiple - indica se é possível colocar mais de um atributo sobre o local de seu aplicativo ou não. Falso por padrão
  2. Herdado - determina se esse atributo pertencerá a herdeiros de classes (no caso de posicionamento sobre a classe base) e métodos substituídos (no caso de posicionamento sobre o método). O padrão é verdadeiro.

Depois disso, você pode carregar os atributos com uma carga útil. Um atributo é uma informação declarativa, o que significa que tudo o que está definido nela deve descrever a construção à qual se relaciona. O atributo não deve conter nenhuma lógica profunda. Pelo processamento dos atributos que você define, os serviços especiais devem ser responsáveis, que apenas os processarão. Mas o fato de o atributo não ter lógica não significa que ele não deva ter métodos.

Um método (função) também é uma informação e também pode descrever um design. E usando o polimorfismo em atributos, você pode fornecer uma ferramenta muito poderosa e conveniente, na qual o usuário pode influenciar as informações usadas por sua ferramenta e certos estágios de execução e processamento. , , , . -, , . , , «» .


-. .

. .

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

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

. , , . , bool inherit ( true ). , ( ). , inherit = flase , true
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/pt468287/


All Articles