C#中的Attribute详解(下)

C#中的Attribute详解(下)

  • 一、Attribute本质
  • 二、Attribute实例化
  • 三、Attribute实例化的独特之处
  • 四、元数据的作用
  • 五、自定义Attribute实例
  • 六、Attribute的附着目标
  • 七、附加问题

一、Attribute本质

从上篇里我们可以看到,Attribute似乎总跟public、static这些关键字(Keyword)出现在一起。莫非使用了Attribute就相当于定义了新的修饰符(Modifier)吗?让我们一窥究竟吧!
示例代码如下:

#define Guo
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;namespace AttributeTest
{class Program{static void Main(string[] args){Func();Console.ReadKey();}[Conditional("Guo")]static void Func(){Console.WriteLine("Hello Attribute!");}}
}

先编译程序,然后使用微软的中间语言反编译器查看MSIL中间语言中static void Func()方法的代码,截图如下:
在这里插入图片描述

可以看出:Attribute本质上就是一个类,它附着在目标对象上最终实例化。

仔细观察中间语言(MSIL)的代码之后,那些被C#语言掩盖的事实,在中间语言中就变得赤身裸体了,Attribute也变得毫无秘密!
图中红色部分指的是Func方法及其修饰符,但Attribute并没有出现在这里。
图中蓝色部分指的是调用mscorlib.dll程序集中System.Diagnostics命名空间中ConditionalAttribute类的含参构造方法,01 00 03 47 75 6F 00 00 实际上是字符串Guo的十六进制形式。
可见,Attribute并不是修饰符,而是一个有着独特实例化形式的类。

除了分析中间语言之外,给方法添加特性时系统给出的提示信息,也可以帮助大家了解Attribute,系统提示截图如下:

在这里插入图片描述

二、Attribute实例化

就像牡蛎天生要吸附在礁石或船底上一样,Attribute的实例一构造出来就必须“粘”在一个什么目标上。
Attribute实例化的语法是相当怪异的,主要体现在以下三点:

不通过new操作符来产生实例,而是使用在方括号里调用构造方法来产生实例。
方括号必须紧挨着放置在被附着目标的前面。
因为方括号里空间有限,所以不能使用对象初始化器给对象的属性(Property)赋值,必须使用含参构造方法对Attribute实例中的属性赋值。
Attribute实例化时尤其要注意的是:

构造函数的参数一定要写。有几个就得写几个,否则类无法正常实例化。
构造函数参数的顺序不能错。调用任何函数都不能改变参数的顺序,除非他有相应的重载(Overload)。因为这个顺序是固定的,有些书里称其为“定位参数”(意即“个数和位置固定的参数”)。
对Attribute实例中的属性的赋值可有可无。它会有一个默认值,并且属性赋值的顺序不受限制。有些书里称属性赋值的参数为“具名参数”。

三、Attribute实例化的独特之处

  1. 他的实例是用.custom声明的。查看中间语法,你会发现.custom是专门用来声明自定义特性的。
  2. 声明Attribute的位置是在函数体内的真正代码(IL_0000至IL_0014)之前。
  3. 这就从“底层”证明了Attribute不是“修饰符”,而是一种实例化方式比较特殊的类。

四、元数据的作用

MSIL中间语言中,程序集的元数据(Metadata)记录了这个程序集里有多少个namespace、多少个类、类里有什么成员、成员的访问级别是什么。元数据是以文本(也就是Unicode字符)形式存在的,使用.NET的反射(Reflection)技术就能把它们读取出来,并形成MSIL中的树状图、VS里的Object Browser视图以及代码自动提示功能,这些都是元数据与反射技术结合的产物。一个程序集(.exe或.dll)能够使用包含在自己体内的元数据来完整地说明自己,而不必像C/C++那样带着一大捆头文件,这就叫作“自包含性”或“自描述性”。

五、自定义Attribute实例

在此我们不使用.Net Framework中的各种Attribute系统特性,而是从头自定义一个全新的Attribute类。
示例代码如下:

namespace AttributeTest
{class Program{static void Main(string[] args){System.Reflection.MemberInfo memberInfo = typeof(Student);System.Reflection.PropertyInfo propertyInfo = typeof (Student).GetProperty("Name");HobbyAttribute hobbyStudent = (HobbyAttribute)Attribute.GetCustomAttribute(memberInfo, typeof(HobbyAttribute));HobbyAttribute hobbyName = (HobbyAttribute) Attribute.GetCustomAttribute(propertyInfo, typeof (HobbyAttribute));if (hobbyStudent != null){Console.WriteLine("类Student的特性");Console.WriteLine("类名:{0}", memberInfo.Name);Console.WriteLine("兴趣类型:{0}", hobbyStudent.Type);Console.WriteLine("兴趣指数:{0}\n", hobbyStudent.Level);}if (hobbyName != null){Console.WriteLine("属性Name的特性");Console.WriteLine("属性名:{0}", propertyInfo.Name);Console.WriteLine("兴趣类型:{0}", hobbyName.Type);Console.WriteLine("兴趣指数:{0}", hobbyName.Level);}Console.ReadKey();}}   [Hobby("Sport",Level = 5)]class Student{[Hobby("Tennis",Level = 3)]public string Name { get; set; }public int Age { get; set; }} 
}
namespace AttributeTest
{class HobbyAttribute:Attribute{//值为null的string是危险的,所以必需在构造函数中赋值public HobbyAttribute(string type){this.Type = type;}public string Type { get; set; }public int Level { get; set; }}
}

为了不让代码太长,上面示例中Hobby类的构造函数只有一个参数,所以对“定位参数”体现的不够淋漓尽致。大家可以为Hobby类再添加几个属性,并在构造函数里多设置几个参数,体验一下Attribute实例化时对参数个数及参数位置的敏感性。
示例运行结果如下:
在这里插入图片描述

六、Attribute的附着目标

Attribute可以将自己的实例附着在什么目标上呢?这个问题的答案隐藏在AttributeTargets这个枚举类型里。
这个枚举类型的可取值集合为:


All          Assembly   Class     Constructor
Delegate       Enum    Event     Field
GenericParameter  Interface   Method    Module
Parameter      Property  ReturnValue  Struct


一共是16个可取值。不过,上面这张表是按字母顺序排列的,并不代表它们真实值的排列顺序。使用下面这个小程序可以查看每个枚举值对应的整数值。

static void Main(string[] args)
{            Console.WriteLine("Assembly\t\t{0}", Convert.ToInt32(AttributeTargets.Assembly));Console.WriteLine("Module\t\t\t{0}", Convert.ToInt32(AttributeTargets.Module));Console.WriteLine("Class\t\t\t{0}", Convert.ToInt32(AttributeTargets.Class));Console.WriteLine("Struct\t\t\t{0}", Convert.ToInt32(AttributeTargets.Struct));Console.WriteLine("Enum\t\t\t{0}", Convert.ToInt32(AttributeTargets.Enum));Console.WriteLine("Constructor\t\t{0}", Convert.ToInt32(AttributeTargets.Constructor));Console.WriteLine("Method\t\t\t{0}", Convert.ToInt32(AttributeTargets.Method));Console.WriteLine("Property\t\t{0}", Convert.ToInt32(AttributeTargets.Property));Console.WriteLine("Field\t\t\t{0}", Convert.ToInt32(AttributeTargets.Field));Console.WriteLine("Event\t\t\t{0}", Convert.ToInt32(AttributeTargets.Event));Console.WriteLine("Interface\t\t{0}", Convert.ToInt32(AttributeTargets.Interface));Console.WriteLine("Parameter\t\t{0}", Convert.ToInt32(AttributeTargets.Parameter));Console.WriteLine("Delegate\t\t{0}", Convert.ToInt32(AttributeTargets.Delegate));Console.WriteLine("ReturnValue\t\t{0}", Convert.ToInt32(AttributeTargets.ReturnValue));Console.WriteLine("GenericParameter\t{0}", Convert.ToInt32(AttributeTargets.GenericParameter));Console.WriteLine("All\t\t\t{0}", Convert.ToInt32(AttributeTargets.All));Console.ReadKey();
}

运行结果如下:
在这里插入图片描述
它们的值并不是步长值为1的线性递增,除了All之外,每个值的二进制形式中只有一位是“1”,其余全是“0”。这就是枚举值的另一种用法——标识位。那么标识位有什么好处呢?
如果我们的Attribute要求既能附着在类上,又能附着在方法上,可以使用C#中的操作符”|”(即按位求“或”)。有了它,我们只需要将代码书写如下:AttributeTargets.Class | AttributeTargets.Method,因为这两个枚举值的标识位(也就是那个唯一的1)是错开的,所以只需按位求或就解决问题了。这样,你就能理解为什么AttributeTargets.All的值是32767了。
默认情况下,当我们声明并定义一个新的Attribute类时,它的可附着目标是AttributeTargets.All。大多数情况下,AttributeTargets.All就已经满足要求了。不过,如果你非要对它有所限制,那就要费点儿周折了。
例如,你想把前面的HobbyAttribute类的附着目标限制为只有“类”和“属性”能使用,则示例代码如下:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
class HobbyAttribute : Attribute
{//HobbyAttribute类的具体实现
}

这里使用Attribute的实例(AttributeUsage)附着在Attribute类(HobbyAttribute)上。Attribute的本质是类,而AttributeUsage又说明HobbyAttribute可以附着在哪些类型上。

七、附加问题

1、如果一个Attribute类附着在了某个类上,那么这个Attribute类会不会随着继承关系也附着到派生类上呢?
2、可不可以像多个牡蛎附着在同一艘船上那样,让一个Attribute类的多个实例附着在同一个目标上呢?
答案:可以。代码如下:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property,Inherited = false,AllowMultiple = true)]
class HobbyAttribute : Attribute
{//HobbyAttribute类的具体实现
}

AttributeUsage这个专门用来修饰Attribute的Attribute,除了可以控制修饰目标外,还能决定被它修饰的Attribute是否可以随宿主“遗传”,以及是否可以使用多个实例来修饰同一个目标!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2659742.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

SAP VA01 创建带wbs号的销售订单包 CJ067的错误

接口错误提示如下 SAP官方 CJ067 124177 - VA01: CJ067 during WBS acct assgmt with a different business area S4的core 刚好能用上 实施 这个note后成功

nestjs入门教程系列(一):让项目先跑起来

nestjs启动基本步骤 Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用的框架。 它使用渐进式 JavaScript,构建并完全支持 TypeScript(但仍然允许开发者使用纯 JavaScript 进行编码)并结合了 OOP(面向对象编程&am…

第二节-数据封装+传输介质

数据传输的形式: 1.电路交换 2.报文交换: 在数据之外,加上能够标识接收者以及发送者的信息 3.分组交换: 依然进行报文交换,不过讲每个数据的大小进行定义 应用层(数据data)->传输层&am…

K8S部署Harbor仓库实战

K8S部署Harbor仓库实战 K8S部署Harbor仓库实战 - 简书 创建文件目录 chartmuseum目录: /var/nfs/data/harbor/chartmuseumdatabase目录: /var/nfs/data/harbor/databasejobservice目录: /var/nfs/data/harbor/jobserviceredis目录: /var/nfs/data/harbor/redisregistry目录:…

深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第五节 引用类型复制问题及用克隆接口ICloneable修复

深入浅出图解C#堆与栈 C# Heaping VS Stacking 第五节 引用类型复制问题及用克隆接口ICloneable修复 [深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈](https://mp.csdn.net/mdeditor/101021023)[深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节…

【头歌实训】kafka-入门篇

文章目录 第1关:kafka - 初体验任务描述相关知识Kafka 简述Kafka 应用场景Kafka 架构组件kafka 常用命令 编程要求测试说明答案代码 第2关:生产者 (Producer ) - 简单模式任务描述相关知识Producer 简单模式Producer 的开发步骤Ka…

Python跳动的爱心完整代码

文章目录 环境需求完整代码详细分析环境需求 python3.11.4PyCharm Community Edition 2023.2.5pyinstaller6.2.0(可选,这个库用于打包,使程序没有python环境也可以运行,如果想发给好朋友的话需要这个库哦~)【注】 python环境搭建请见:https://want595.blog.csdn.net/arti…

深入理解Mysql MHA高可用集群搭建:从实验到实战

1. 简介 MHA(Master High Availability)是一个高效的开源MySQL高可用性解决方案。由日本开发者yoshinorim(前DeNA员工,现在Facebook)创建,MHA支持MySQL的主从复制架构,自动化主节点故障转移。当…

支付宝 v3 验签如何实现

上次给大家介绍了 支付宝 v3 自签名如何实现,这次顺便再把验签也写一下。 为什么要验签 说起为什么要验签,如果要详细一点解释的话,可以写很多很多...... 我们就简单一点来解释:验签可以证明接收到的信息是支付宝给我的&#xf…

【信息安全原理】——拒绝服务攻击及防御(学习笔记)

📖 前言:拒绝服务攻击(Denial of Service, DoS)是一种应用广泛、难以防范、严重威胁网络安全(破坏可用性)的攻击方式。本章主要介绍DoS的基本概念、攻击原理及防御措施。 目录 🕒 1. 定义&#…

sonarqube安装踩坑记录

如果用java1.8和mysql,则sonarqube版本不能超过7.8,看这里。 sonarqube7.8安装包地址: https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-7.8.zip 安装步骤: 1、下载sonarqube安装包 wget https://binari…

计算机毕业设计---ssm+mysql+jsp实现的校园二手市场交易平台源码

项目介绍 本系统主要实现的功能有: 前台:(1)二手物品信息查看、搜索。 (2)学生注册登录、个人信息修改。 (3)二手物品信息发布、编辑。 (4)二手物品评论、回…

mybatisX自动生成sql语句,尝试测试方法报错

今天我使用mybatisx自定义mapper方法生成sql语句后,在测试时报错 错误是MyBatis 无法找到映射的语句(Statement)引起的 我是这样操作的,在mapper接口自定义了一个方法 然后alt加enter,自动生成sql 结果 mapper.xml文件…

手机流量卡推广分销网站php源码,多功能的号卡推广分销管理系统

源码简介 拥有多个接口,包括运营商接口,并支持无限三级代理。 最简单易用的PHP系统,它自带自动安装向导,可以让你轻松安装和部署。 该系统集成了多个第三方接口资源,能够满足你的不同需求。采用全系统双色主题&…

MYSQL 索引结构 B+树 hash索引

B-Tree树 当节点存在五个key时,中间的key向上分裂形成树 B树 所有的数据都会出现在叶子节点,叶子节点形成一个单向链表 哈希索引 优点

Vulnhub-Al-Web-1.0 靶机复现完整过程

一、信息收集 1.主机发现 arp-scan -l2.端口扫描 nmap -sV -p- 192.168.200.16PORTSTATESERVICEVERSIONMAC Address80/TCPOpenhttpApache httpd00:0C:29:C4:1B:78 (VMware) 3.目录扫描 python dirsearch.py -u http://192.168.200.16扫描出来这两个文件,首先先…

java基础-回忆性记录

java基础 Java概括 jaava是一种计算机交流的高级编程语言,1995年java衍生,詹姆斯高斯林被世人称之为java之父。 java语言具有跨平台性 java程序并非可以直接运行的,在java程序编译完成后会形成与编译无关的class文件。Java具有跨平台性&a…

ARM CCA机密计算软件架构之内存加密上下文(MEC)

内存加密上下文(MEC) 内存加密上下文是与内存区域相关联的加密配置,由MMU分配。 MEC是Arm Realm Management Extension(RME)的扩展。RME系统架构要求对Realm、Secure和Root PAS进行加密。用于每个PAS的加密密钥、调整或加密上下文在该PAS内是全局的。例如,对于Realm PA…

中国蚁剑-antSword

1.简介 蚁剑是一款开源的跨平台Webshell管理工具,它是一个开源的远程管理工具,主要面向于合法授权的渗透测试安全人员以及进行常规操作的网站管理员。蚁剑提供了一个图形化界面,可以通过简单的操作连接和控制目标设备或系统。 蚁剑具有以下…

QT应用篇 三、QML自定义显示SpinBox的加减按键图片及显示值效果

QT应用篇 一、QT上位机串口编程 二、QML用Image组件实现Progress Bar 的效果 三、QML自定义显示SpinBox的加减按键图片及显示值效果 文章目录 QT应用篇前言一、qml需求二、使用组件1.SpinBox组件2.SpinBox中QML的使用 总结 前言 记录自己学习QML的一些小技巧方便日后查找 QT的…