C#中的Attribute详解(下)

news2024/11/27 17:52:52

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://www.coloradmin.cn/o/1349227.html

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

相关文章

Linux基础知识点(五-信号)

一、信号的基本概念 1.1 信号的概念 信号(signal),又称为软中断信号,用于通知进程发生了异步事件,它是Linux系统响应某些条件而产生的一个事件,它是在软件层次上对中断机制的一种模拟,是一种异…

12.29最小生成数K算法复习(注意输入输出格式),校园最短路径(通过PRE实现路径输出,以及输入输出格式注意)

7-2 最小生成树-kruskal算法 分数 15 const int maxn 1000; struct edge {int u, v, w; }e[maxn]; int n, m, f[30]; bool cmp(edge a, edge b) {return a.w < b.w; } int find(int x) {if (f[x] x) {return x;}else {f[x] find(f[x]);return f[x];} } //int arr[100…

简单FTP客户端软件开发——VMware安装Linux虚拟机(命令行版)

VMware安装包和Linux系统镜像&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1UwF4DT8hNXp_cV0NpSfTww?pwdxnoh 提取码&#xff1a;xnoh 这个学期做计网课程设计【简单FTP客户端软件开发】需要在Linux上配置 ftp服务器&#xff0c;故此用VMware安装了Linux虚拟机&…

数据结构模拟实现LinkedList双向不循环链表

目录 一、双向不循环链表的概念 二、链表的接口 三、链表的方法实现 &#xff08;1&#xff09;display方法 &#xff08;2&#xff09;size方法 &#xff08;3&#xff09;contains方法 &#xff08;4&#xff09;addFirst方法 &#xff08;5&#xff09;addLast方法 …

python开发的app有哪些,python如何开发小软件

这篇文章主要介绍了python开发的app有哪些&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 Python 无处不在&#xff0c;可以说是现代的 C 编程语言&#xff0c;你可以在任何地…

JavaScript的三种引入的方式

目录 (一).什么是JS1.1JS的特点1.2JS的组成 (二).JS引用的三种方式2.1标签引用&#xff08;或嵌入式)2.2文件引用&#xff08;外链式&#xff09;2.3行内式 (三).JS三种引用方式的优缺点1.行内方式&#xff1a;2.标签引用&#xff08;或嵌入式&#xff09;&#xff1a;3.文件引…

Codeforces Round 900 (Div. 3)(A-F)

比赛链接 : Dashboard - Codeforces Round 900 (Div. 3) - Codeforces A. How Much Does Daytona Cost? 题面 : 思路 : 在序列中只要找到k&#xff0c;就返回true ; 代码 : #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)…

[Redis实战]分布式锁

四、分布式锁 4.1 基本原理和实现方式对比 分布式锁&#xff1a;满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁&#xff0c;只要大家使用的是同一把锁&#xff0c;那么我们就能锁住线程&#xff0c;不让线程进行&#xf…

机械过滤器(石英砂过滤器)和多介质过滤器的区别 工作原理动画

​ 1&#xff1a;机械过滤器多介质石英砂过滤器介绍 机械过滤器&#xff1a;预处理水质的关键设备 机械过滤器&#xff0c;也被称为压力式过滤器&#xff0c;是纯水制备过程中不可或缺的预处理设备。它在水处理系统中扮演着重要的角色&#xff0c;能够有效地去除水中的悬浮物…

Ubuntu(WSL)创建用户并赋予权限

查看当前用户 创建新用户 1&#xff09;执行如下命令创建新用户 sudo adduser mysql57 // mysql57 为新用户名&#xff0c;可自定义 2&#xff09;若待创建用户已存在&#xff0c;则先删除已存在用户&#xff0c;然后创建新用户 // 删除用户名 userdel mysql57// 删除组…

lambda表达式和包装器

正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 我们在使用库里的排序算法时如果排序的是自定义类型或者库里默认的排序不能满足我们则需求&…

2.2 设计FMEA步骤二:结构分析

2.2.1 目的 设计结构分析的目的是将设计识别和分解为系统、子系统、组件和零件,以便进行技术风险分析。其主要目标包括: 可视化分析范围结构化表示:方块图、边界图、数字模型、实体零件识别设计接口、交互作用和间隙促进顾客和供应商工程团队之间的协作(接口责任)为功能分…

PyTorch官网demo解读——第一个神经网络(4)

上一篇&#xff1a;PyTorch官网demo解读——第一个神经网络&#xff08;3&#xff09;-CSDN博客 上一篇我们聊了手写数字识别神经网络的损失函数和梯度下降算法&#xff0c;这一篇我们来聊聊激活函数。 大佬说激活函数的作用是让神经网络产生非线性&#xff0c;类似人脑神经元…

Delphi6函数大全4-SysUtils.pas

Delphi6函数大全4-SysUtils.pas首部 function FormatFloat(const Format: string; Value: Extended): string; $[SysUtils.pas功能 返回浮点数类型以指定格式字符串Format转换成字符串说明 FormatFloat(,.00, 1234567890) 1,234,567,890.00参考 function …

[SSD 测试 1.3] 消费级SSD全生命周期测试

依公知及经验整理,原创保护,禁止转载。 专栏 《深入理解SSD》 <<<< 返回总目录 <<<< 构建消费级SSD全生命周期测试,开展性能测试、兼容性测试、功能测试、环境应力测试、可靠性测试、电器检测。 以忆联消费级存储实验室为例,消费级存储实验室面积…

思维链COT原理探究

要进行因果分析&#xff0c;需要把思维链中的不同元素拆解开来&#xff0c;然后通过控制变量实验&#xff0c;来研究不同元素对COT效果的影响。以下两篇论文的核心差异就在于: COT的变量拆解&#xff0c;以及控制变量的实验方式。 结合两篇论文的实验结论&#xff0c;可能导致…

Java技术栈 —— Redis的雪崩、穿透与击穿

Java技术栈 —— Redis的雪崩、穿透与击穿 〇、实验的先导条件&#xff08;NginxJmeter&#xff09;一、Redis缓存雪崩、缓存穿透、缓存击穿1.1 雪崩1.2 穿透1.3 击穿 二、Redis应用场景——高并发2.1 单机部署的高并发问题与解决&#xff08;JVM级别锁&#xff09;2.2 集群部署…

RK3568平台 Android13 GKI架构开发方式

一.GKI简介 GKI&#xff1a;Generic Kernel Image 通用内核映像。 Android13 GMS和EDLA认证的一个难点是google强制要求要支持GKI。GKI通用内核映像&#xff0c;是google为了解决内核碎片化的问题&#xff0c;而设计的通过提供统一核心内核并将SoC和板级驱动从核心内核移至可加…

29 UVM Command Line Processor (CLP)

随着设计和验证环境的复杂性增加&#xff0c;编译时间也增加了&#xff0c;这也影响了验证时间。因此&#xff0c;需要对其进行优化&#xff0c;以便在不强制重新编译的情况下考虑新的配置或参数。我们已经看到了function or task如何基于传递参数进行行为。类似地&#xff0c;…

30 UVM Adder Testbench Example

1 Adder Design 加法器设计在时钟的上升沿产生两个变量的加法。复位信号用于clear out信号。注&#xff1a;加法器可以很容易地用组合逻辑开发。引入时钟和重置&#xff0c;使其具有测试台代码中时钟和重置的样子/风格。 module adder(input clk, reset, input [7:0] in1, in…