C#属性:关于所有方面

您好读者。 本文从各个方面描述了属性-从规范,属性的含义和定义,创建自己的属性以及如何使用它们,最后在运行时添加属性以及最有用和最有趣的现有属性。 如果您对C#中的属性主题感兴趣,欢迎使用cat。


目录内容


  1. 引言 定义和分配属性
  2. 运行时支持有趣的属性。 在这里,将提供有关各种属性的简短信息,这些属性的存在鲜为人知,甚至很少使用。 由于这绝对是不切实际的信息,因此不会有太多的抱怨(与我对不适用的知识的热情相反)
  3. 一些有用的鲜为人知的属性。
  4. 定义属性并进行处理。 在运行时添加属性

引言


与往常一样,从定义和规范开始。 这将有助于理解和实现各个级别的属性,这反过来对于找到适合它们的正确应用程序非常有用。

首先定义元数据。 数据是描述和引用CTS定义的类型的数据。 元数据的存储方式独立于任何特定的编程语言。 因此,元数据提供了一种通用的机制,可以在需要它的工具(编译器和调试器,以及程序本身)之间以及在VES之间交换有关程序的信息,以供使用。 元数据包含在程序集清单中。 它们可以与IL代码一起存储在PE文件中,也可以存储在单独的PE文件中,在该文件中只有程序集清单。
属性是包含描述性信息的类型或其成员(或其他语言构造)的特征。 尽管最常见的属性是预定义的,并且在元数据中具有特定的格式,但是也可以将自定义属性添加到元数据中。 属性是可交换的,即 它们在元素上的声明顺序并不重要

从语法角度(在元数据中)来看,有以下属性

  1. 在IL中使用特殊语法。 例如,关键字是属性。 对于他们来说,IL中有一种特殊的语法。 其中有很多;列出所有内容都没有意义
  2. 使用广义语法。 这些包括用户和库属性。
  3. 安全属性。 其中包括从SecurityAttribute继承的属性(直接或间接)。 它们以特殊方式处理。 IL中有一种特殊的语法,允许您创建直接描述这些属性的xml。

例子


包含上述所有类型的属性的C#代码
[StructLayout(LayoutKind.Explicit)] [Serializable] [Obsolete] [SecurityPermission(SecurityAction.Assert)] public class Sample { } 


结果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*/} } 


如您所见,StructLayoutAttribute具有特殊的语法,因为在IL中它表示为“显式”。 ObsoleteAttribute使用通用语法-在IL中以“ .custom”开头。 SecurityPermissionAttribute作为安全属性已变成“ .permissionset assert”。

用户属性将用户信息添加到元数据。 此机制可用于在编译时存储特定于应用程序的信息,并在运行时访问该信息,或用于其他工具的读取和分析。 尽管任何用户定义的类型都可以用作属性,但CLS一致性要求属性继承自System.Attribute。 CLI预定义了一些属性,并使用它们来控制运行时行为。 一些语言定义属性以表示未在CTS中直接表示的语言功能。

如上所述,属性存储在元数据中,而元数据又在编译阶段生成,即 在PE文件中输入(通常是* .dll)。 因此,您只能在运行时修改可执行文件才能在运行时添加属性(但是自更改程序的时间已久)。 因此,它们不能在执行阶段添加,但这并不完全准确。 如果我们形成程序集,在其中定义类型,那么我们可以在执行阶段创建一个新类型并将其挂起属性。 因此,从形式上讲,我们仍然可以在运行时添加属性(示例将在最底部)。

现在介绍一些限制


如果由于某种原因,在同一程序集中有两个名称分别为Name和NameAtribute的属性,则无法将它们放在第一个。 使用[名称](即不带后缀)时,编译器表示看到不确定性。 使用[NameAttribute]时,我们将放置NameAttribute,这是合乎逻辑的。 对于这种神秘的情况,有一种特殊的语法,命名时缺乏想象力。 要放置第一个不带后缀的版本,可以在属性名称[@Name]之前指定狗的符号(即[Name]是个笑话,没有必要)。

可以将自定义属性添加到除自定义属性之外的任何内容。 这是指元数据,即 如果将属性放在属性类上方的C#中,则在元数据中它将引用该类。 但是您不能将属性添加到“公共”。 但是您可以使用程序集,模块,类,值类型,枚举,构造函数,方法,属性,字段,事件,接口,参数,委托,返回值或广义参数。 下面的示例显示了如何将属性放在特定构造上的明显示例,但不是非常示例。

属性声明语法
 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; } } } 


属性具有两种类型的参数-命名参数和位置参数。 位置参数包括构造函数参数。 要命名-具有可访问设置器的公共属性。 而且,这些不只是形式名称;在属性名称后的方括号中声明属性时,可以指示所有参数。 命名的是可选的。

参数类型
 [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 { } 


该属性的有效参数(两种类型)必须是以下类型之一:

  1. bool,byte,char,double,float,int,long,short,string以及其他基本类型(十进制除外)
  2. 对象
  3. 系统类型
  4. 枚举
  5. 以上任何类型的一维数组

这主要是因为它应该是一个编译时常量,并且上面的类型可以接受该常量(通过接受一个对象,我们可以传递int)。 但是出于某种原因,该参数不能为ValueType类型,尽管从逻辑角度来看这是可能的。

用户属性有两种:真正的定制属性和伪定制
在代码中,它们看起来相同(它们在语言结构上方的方括号中表示),但是它们的处理方式不同:

  1. 原始用户属性直接存储在元数据中; 属性参数按原样存储。 它们在运行时可用,并保存为一组字节(我不得不提醒您它们在编译时是已知的)
  2. 可以识别伪用户属性,因为其名称是特殊列表之一。 而不是直接将其数据存储在元数据中,而是对其进行分析并用于设置元数据表中的位或字段,然后丢弃该数据,无法进一步接收该数据。 在运行时检查元数据表的速度要比真正的用户属性快,并且需要较少的存储空间来存储信息。

伪用户属性不可见
 [Serializable] [StructLayout(LayoutKind.Explicit)] public class CustomPseudoCustom { } class Program { static void Main() { var onlyCustom = typeof(CustomPseudoCustom).GetCustomAttributes(); // SerializableAttribute } } 


大多数用户属性是在语言级别引入的。 它们由运行时存储并返回,而运行时对这些属性的含义一无所知。 但是,所有伪用户属性以及一些用户属性对于编译器和CLI都是特别重要的。 因此,我们继续下一部分。

启用运行时的属性


本部分仅提供参考,如果您对使用运行时没有兴趣,则可以滚动到下一部分。

下表列出了伪用户属性和特殊用户属性(CLI或编译器以特殊方式处理它们)。

伪用户属性(无法通过反射获得)。
CLI属性:
属性内容描述
AssemblyAlgorithmIDAttribute写入使用的哈希算法的标识符。 设置Assembly.HashAlgId字段
AssemblyFlagsAttribute为相应的程序集写入标志。 设置Assembly.Flags字段
DllImportAttribute提供有关在非托管库中实现的代码的信息。 设置相应方法的Method.Flags.PinvokeImpl位; 向ImplMap添加一个新条目(通过设置MappingFlags,MemberForwarded,ImportName和ImportScope的值)
StructLayoutAttribute允许您显式设置放置引用字段或重要类型的字段的方法。 设置类型的TypeDef.Flags.LayoutMask字段。 它还可以设置TypeDef.Flags.StringFormatMask,ClassLayout.PackingSize和ClassLayout.ClassSize字段
FieldOffsetAttribute定义引用或有效类型中字段的字节偏移量。 设置对应方法的FieldLayout.OffSet的值。
归因指示参数作为[in]参数传递。 设置相应参数的Param.Flags.In位。
超越属性指示参数作为[out]参数传递。 设置相应参数的Param.Flags.Out位。
Marshalasattribute定义如何在托管和非托管代码之间封送数据。 设置字段的Field.Flags.HasFieldMarshal位(或为参数设置Param.Flags.HasFieldMarshal位); 将一个条目添加到FieldMarshal表中(通过设置Parent和NativeType的值)
MethodImplAttribute定义方法的实现细节。 设置相应方法的Method.ImplFlags值


CLS属性-语言必须支持它们:
属性内容描述
AttributeUsageAttribute用于指示如何使用属性。
ObsoleteAttribute指示不应使用该项目。
CLSCompliantAttribute指示项目是否声明为符合CLS。

杂项有趣
属性内容描述
ThreadStaticAttribute提供与流相关的类型字段
条件属性根据编译条件(在/ define中指定)将方法标记为已调用。 如果不满足条件,则将不会调用该方法(也不会将其编译为IL)。 只有void方法可以被标记。 否则,将发生编译错误。
DecimalConstantAttribute在元数据中存储十进制常数值
DefaultMemberAttribute定义默认情况下与InvokeMember方法一起使用的类的成员。
CompilationRelaxationsAttribute指示指令检查的异常是严格的还是宽松的。 当前,您只能传递NoStringInterning参数,该参数将程序集标记为不需要字符串文字内联。 但是这种机制仍然可以使用。
FlagsAttribute指示是否应将枚举视为位标志的属性
IndexerNameAttribute指定将在不直接支持此功能的编程语言中使用索引器的名称。
ParamArrayAttribute指示该方法接受可变数量的参数。

有用的属性


软件产品开发的组成部分是调试。 而且在大型而复杂的系统中,运行相同的方法并监视对象的状态通常需要数十次甚至数百次。 同时,在20点的时间,它已经开始特别激起需要将一个对象扩展到400倍以查看一个变量的值并再次重新启动该方法的需要。
为了更安静,更快速地调试,您可以使用修改调试器行为的属性。

DebuggerDisplayAttribute指示类型或其成员在调试器变量窗口中的显示方式(不仅限于此)。

构造函数的唯一参数是具有显示格式的字符串。 括号之间的内容将被计算。 格式就像插值的字符串,只是没有美元。 您不能在计算值中使用指针。 顺便说一句,如果您有一个重写的ToString,则将显示它的值,就像它在此属性中一样。 如果同时有ToString和属性,则从属性获取值。


DebuggerBrowsableAttribute定义在调试器变量窗口中如何显示字段或属性。 接受DebuggerBrowsableState,它具有3个选项:

  • 从不-调试期间完全不显示该字段。 扩展对象的层次结构时,不会显示此字段
  • 折叠-字段尚未解决,但可以扩展。 这是默认行为。
  • RootHidden-字段本身未显示,但显示了其组成的对象(用于数组和集合)



DebuggerTypeProxy-如果一天要在调试器中查看该对象数百次,您可能会感到困惑,花3分钟创建一个代理对象,该代理对象应按原样显示原始对象。 通常,要显示的代理对象是内部类。 实际上,它将显示而不是目标对象。



其他有用的属性

ThreadStatic-一个属性,允许您为每个线程自己创建一个静态变量。 为此,请将属性放在静态字段上方。 值得记住的一个重要细微差别-静态构造函数的初始化将仅执行一次,并且变量将在静态构造函数执行的线程中更改。 在其他情况下,它将保持默认状态。 (PS。如果您需要这种行为,我建议您看一下ThreadLocal类)。

关于发动机舱的细微差别。 在Linux和Windows中,流本地都有一个内存区域(分别为TLSTSD )。 但是,这些区域本身很小。 因此,将创建一个ThreadLocalInfo结构,并将指针放在TLS中。 因此,仅使用一个插槽。 结构本身包含3个字段-Thread,AppDomain,ClrTlsInfo。 我们对第一个感兴趣。 正是使用ThreadLocalBlock和ThreadLocalModule来组织流静态信息在内存中的存储。

通过这种方式:

  • 引用类型-位于堆上的ThreadStaticHandleTable(由ThreadLocalBlock类支持)保持与它们的链接。
  • 结构-打包并存储在托管堆中以及引用类型
  • 原始有效类型存储在ThreadLocalModule的一部分非托管内存中

好吧,由于我们正在谈论这一点,因此值得一提的是异步方法。 细心的读者可能会注意到,如果我们使用异步,那么延续不一定会在同一线程中执行(我们可以影响执行上下文,但不会影响线程)。 相应地,如果我们使用ThreadLocal,我们将得到废话。 在这种情况下,建议使用AsyncLocal。 但是这篇文章不是关于这个的,所以我们走得更远。

InternalsVisibleTo-允许您指定程序集,该程序集将对标记为internal的元素可见。 看起来,如果某些程序集需要某些类型及其成员,则可以简单地将其标记为公开,而不用标明它们。 但是好的架构意味着隐藏实现细节。 但是,某些基础设施可能需要它们,例如测试项目。 使用此属性,可以同时支持封装和所需的测试覆盖率百分比。

HandleProcessCorruptedStateExceptions-允许您吓胆小的程序员并捕获损坏状态的异常。 默认情况下,对于此类异常,CLR不会捕获。 通常,最好的解决方案是让应用程序崩溃。 这些危险的异常表明进程内存已损坏,因此使用此属性是一个非常糟糕的主意。 但是在某些情况下,对于本地开发来说,设置此属性一段时间会很有用。 要捕获损坏状态的异常,只需将此属性放在方法上方。 并且,如果已经达到使用此属性的程度,则建议(不过,一如既往)捕获一些特定的异常。

DisablePrivateReflection-使程序集的所有私有成员无法进行反射。 该属性被放在装配上。

定义属性


不只是因为本节是最后一部分。 毕竟,了解在哪种情况下使用属性会有所帮助的最佳方法是查看已使用的属性。 当您考虑自己的属性时,很难说出正式的规则。 通常,它们被用作有关类型/其成员或完全不同的实体共有的另一种语言构造的附加信息。 例如,所有用于序列化/ ORM /格式化等的属性。 由于这些机制广泛应用于完全不同的类型,而相应机制的开发人员通常不知道这些类型,因此属性的使用是使用户能够为该机制提供声明性信息的好方法。

使用属性可以分为两个部分:

  1. 创建一个属性并使用它
  2. 获取属性并对其进行处理

创建一个属性并使用它


要创建您的属性,从System.Attribute继承就足够了。 在这种情况下,建议遵循上述命名方式-在Attribute上结束类名。 但是,如果省略此后缀,则不会有任何错误。 如前所述,属性可以具有两种类型的参数-位置参数和命名参数。 它们的应用逻辑与该类的构造函数的属性和参数相同-将创建没有合理“默认”值的对象所需的值放在位置(即构造函数)中。 可以合理使用的默认值(通常会使用)可以更好地区分为已命名的值(即属性)。

创建属性的重要意义在于其应用位置的限制。 AttributeUsageAttribute用于此目的。 必需的参数(位置)是AttributeTarget,它确定使用属性的位置(方法,程序集等)。 可选(命名)参数为:

  1. AllowMultiple-指示是否可以在其应用程序位置放置多个属性。 默认为假
  2. Inherited-继承-确定此属性是否属于类的继承者(如果放置在基类上)和重写的方法(如果放置在方法上)。 默认值为true。

之后,您可以使用有效负载加载属性。 属性是声明性信息,这意味着其中定义的所有内容都应描述与其相关的构造。 该属性不应包含任何深层逻辑。 对于您定义的属性的处理,应负责仅处理它们的特殊服务。 但是,属性不应该包含逻辑这一事实并不意味着它不应该包含方法。

方法(功能)也是信息,也可以描述设计。 并且,通过在属性中使用多态性,您可以提供一个功能强大且方便的工具,用户可以在其中影响工具所使用的信息以及执行和处理的某些阶段。在这种情况下,他将不需要产生类,注入依赖项,成本工厂及其将创建这些类的接口。创建一个单独的继承类就足够了,该继承类封装了与之相关的元素的工作细节。但是,通常,具有几个属性的常规ROSO属性就足够了。

检索和处理属性


接收到的属性的处理取决于特定情况,并且可以完全不同的方式完成。为此很难给出有用的功能和技巧。

在运行时使用反射获得属性。有多种方法可从特定元素获取属性。

但是,一切都源自ICustomAttributeProvider接口。它由Assembly,MemberInfo,Module,ParameterInfo等类型实现。反过来,MemberInfo的后继者是Type,EventInfo,FieldInfo,MethodBase,PropertyInfo。

该界面只有3个功能,而且不太方便。它们使用数组(即使我们知道只能有一个属性),也不按类型(它们使用对象)进行参数化。因此,您几乎不必直接访问此接口的功能(我从未说过,因为我不想被分类)。为了易于使用,有一个CustomAttributeExtensions,其中有许多用于各种类型的扩展方法,这些扩展方法可以执行简单的转换操作,选择单个值等等,从而使开发人员摆脱了这种需求。同样,这些方法在Attribute类中可以作为静态使用,具有忽略继承参数(对于非遵从者)的最有用功能。

下面列出了使用的主要功能。指示哪种类型扩展方法的第一个参数,我省略了。另外,无论在何处指定boolInherit参数,都将存在重载(默认值为true)。此参数指定在执行方法时(如果在重写的方法上使用)是应考虑父类的属性还是基方法的属性。如果在属性Inherit = flase中,那么即使将其设置为true也无助于考虑基类的属性
方法名称内容描述
GetCustomAttributes <LogAttribute>(布尔继承)获取指定类型的属性的枚举。如果该属性为1,则将返回1个元素的枚举
GetCustomAttribute <LogAttribute>(布尔继承)返回指定类型的单个属性。如果有多个,则抛出System.Reflection.AmbiguousMatchException:找到相同类型的多个自定义属性
GetCustomAttributes()返回所有类型的属性的枚举
GetCustomAttributesData()返回枚举CustomAttributeData,其中包含允许您获取构造函数,参数(命名和位置),构造函数参数的属性
IsDefined(类型attrType,布尔继承)如果在元素上声明了属性,则返回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/zh-CN468287/


All Articles