C# 特性(Attribute)总结

news2024/11/18 12:48:09

目录

特性是什么?

如何使用特性?

(1).Net 框架预定义特性

(2)自定义特性

为什么要使用特性?

特性的应用

特性实现枚举展示描述信息


特性是什么?

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

1 特性定义:特性其实是一个类,是直接/间接继承自Attribute,约定俗成是以Attribut结尾,在标记的时候以[   ]包裹,尾部的Attribut可以省略掉;

2 特性的作用:可以增加额外信息,增加额外功能,但让特性生效需要使用反射

以上都是较为官方的解释,从本人理解来看:特性的作用就是对程序中所标记元素的另加描述和限限制。就像生活中的坐火车的车票一样,对该行程进行了进一步描述(如哪个站,目的地,出发时间等),同时也对描述信息进行了限制(如你错过发车时间就不能上车),出现任何差错都会视为异常。

如何使用特性?

(1).Net 框架预定义特性

  • Conditional:起条件编译的作用,只有满足条件,才允许编译器对它的代码进行编译。一般在程序调试的时候使用
  • DllImport: 用来标记费.net的函数,表明该方法在一个外部的DLL中定义。
  • Obsolete: 这个属性用来标记当前的方法已经废弃,不再使用。
  • Serializable:用来标记该对象可以被序列化。
  • AttributeUsage :描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型:

 /// <summary>
/// 自定义特性
/// AllowMultiple =true:标记在特性上的特性,其实是对特性的一种约束;
/// Inherited =true:约束当前特性是否可以继承
/// AttributeTargets.All:当前特性可以标记在所有的元素上
/// AttributeUsage:在定义特性的时候,对特性的一种约束
/// </summary>
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]
public class CustomAttribute : Attribute
{
        public CustomAttribute()
        {

        }
        
    }
}

(2)自定义特性

.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。创建并使用自定义特性包含四个步骤:

  1. 声明自定义特性
  2. 构建自定义特性
  3. 在目标程序元素上应用自定义特性
  4. 通过反射调用特性

▲自定义一个特性:

  • 定义一个类并且这个类派生自Attribute。
  • 类的名字以Attribute后缀结构。
  • 为了安全性,可以用一个sealed修饰成一个密封类型(非必须的),以防止被其他类所继承(可以参考预定义特性的源码,其都被sealed所修饰)
using System;

namespace MyAttribute
{    
    public sealed class CustomAttribute : Attribute
    {
        
    }
}

▲通过反射调用标记到字段,属性,方法上的特性:

通过反射,从类型,属性,方法都可以获取特性实例,要求先IsDefined检测再获取实例化。

public static void ReflectionArrtibute<T>(T t) where T : Class
{
      Type type = t.GetType();//通过参数来获取Type类型
      if (type.IsDefined(typeof(CustomAttribute),true)) //反射调用特性,记得一定要先判断,然后再获取
      { 
          foreach (CustomAttribute attribute in type.GetCustomAttributes()) //把所有标记的特性都实例化了
          {
              Console.WriteLine($"attribute.Id={attribute.Id}");
              Console.WriteLine($"attribute.Name={attribute.Name}");
              attribute.Do();
          }
          ///循环获取所有的属性上标记的特性,调用特性中的元素
          foreach (PropertyInfo prop in type.GetProperties())
          { 
              if (prop.IsDefined(typeof(CustomAttribute), true))
              {
                  foreach (CustomAttribute attribute in prop.GetCustomAttributes()) //把所有标记的特性都实例化了
                  {
                      Console.WriteLine($"attribute.Id={attribute.Id}");
                      Console.WriteLine($"attribute.Name={attribute.Name}");
                      attribute.Do();
                  }
              }
          }
          ///循环获取所有的字段上标记的特性,调用特性中的元素
          foreach (FieldInfo field in type.GetFields())
          {
              if (field.IsDefined(typeof(CustomAttribute), true))
              {
                  foreach (CustomAttribute attribute in field.GetCustomAttributes()) //把所有标记的特性都实例化了
                  {
                      Console.WriteLine($"attribute.Id={attribute.Id}");
                      Console.WriteLine($"attribute.Name={attribute.Name}");
                      attribute.Do();
                  }
              }
           }
          ///循环获取所有方法上标记的特性,调用特性中的元素
          foreach (MethodInfo method in type.GetMethods())
          {
              if (method.IsDefined(typeof(CustomAttribute), true))
              {
                  foreach (CustomAttribute attribute in method.GetCustomAttributes()) //把所有标记的特性都实例化了
                  {
                      Console.WriteLine($"attribute.Id={attribute.Id}");
                      Console.WriteLine($"attribute.Name={attribute.Name}");
                      attribute.Do();
                  }
              }
          } 
}

为什么要使用特性?

某些时候我们在程序处理过程中为了程序更加健全及安全需要校验一些参数的合法性,比如邮件格式校验等类似的需求,以下以邮件合法及公司CompanyId的范围只能在1000~10000范围为例。刚开始我们最直接也最先想到的就是传统的方式写参数校验。如下所示:

       if (string.IsNullOrWhiteSpace(user.Email))  //:这里只判断了邮箱为空,省略了邮箱合法性判断
        {
         Console.WriteLine("Email 参数不符合!");
         //:这里不执行保存,提示用户参数不合法
        }
        if (user.CompanyId < 1000 || user.CompanyId > 10000)
        {
          Console.WriteLine("CompanyId 参数不符合,CompanyId范围只能是1000~10000");
          //:这里不执行保存,提示用户参数不合法
        }

问题:假如某一天业务需求发生了变化,新增了一个参数的校验或者CompanyId的范围发生改变,我们就需要修改代码。

解决:使用特性:可以在不破坏类型封装的前提下,为对象增加额外的信息,执行额外的行为。通过特性我们把公共逻辑移出去,只完成私有逻辑,具体操作流程如下:

1、考虑到程序后期可能会涉及到多种参数的校验,每种参数要实现的校验规则各不相同,所以我们定义一个抽象类,抽象类中定义一个抽象方法,继承自Attribute。

    public abstract class AbstractValidateAttribute : Attribute
    {
        public abstract bool Validate(object oValue);
    }

2、分别定义两个类用于实现校验邮箱和判断公司ID范围的逻辑,并继承自上面的抽象类。

    //邮箱合法
    [AttributeUsage(AttributeTargets.Property)]
    public  class EmailValidateAttribute:AbstractValidateAttribute
    {
        public override bool Validate(object value)
        {
            if (value!=null)
            {
                return true;//实际的判断
            }
            else
            {
                return false;
            }
        }
    }
    //公司ID在1000~10000范围
    [AttributeUsage(AttributeTargets.Property)]
    public class IntValidateAttribute:AbstractValidateAttribute
    {
        private int _Min = 0;
        private int _Max = 0;

        public IntValidateAttribute(int min, int max)
        {
            this._Min = min;
            this._Max = max;
        }

        public override bool Validate(object oValue)
        {
            return oValue != null && int.TryParse(oValue.ToString(), out int num) && num >= this._Min && num <= this._Max;
        }
    }

3、通过反射调用特性(采用泛型)

 public  class BaseDAL
    {
        public static void Save<T>(T t)
        {
            Type type = t.GetType();
            PropertyInfo[] T2 = type.GetProperties();
            bool isSafe = true;
            {
                foreach (var property in type.GetProperties())
                {
                    //特性类的实例化就在反射发生的时候
                    object[] oAttributeArray = property.GetCustomAttributes(typeof(AbstractValidateAttribute), true);
                    foreach (var oAttribute in oAttributeArray)
                    {
                        //转为基类对象
                        AbstractValidateAttribute validateAttribute = oAttribute as AbstractValidateAttribute;
                        isSafe = validateAttribute.Validate(property.GetValue(t));
                        if (isSafe)
                        {
                            Console.WriteLine($"{oAttribute.ToString()}保存到数据库");
                        }
                        else
                        {
                            Console.WriteLine($"{oAttribute.ToString()}数据不合法");
                        }
                    }
                   
                }
            }
          
        }
    }

4、构建业务类UseModel,并标记相关特性特性

 public  class UseModel
   {
        //邮箱
        [EmailValidate]
        public string Email { get; set; }
        //企业ID
        [IntValidate(1000,10000)]
        public int CompanyID { get; set; }

        public UseModel(string email,int id)
        {
            this.Email = email;
            this.CompanyID = id;
        }
    }

5、主程序调用

        static void Main(string[] args)
        {
            UseModel useModel = new UseModel("小米", 120000);
            BaseDAL.Save<UseModel>(useModel);
        }

 使用特性后,如果新增不同参数类型的校验,只需要新增对应的类,继承自抽象基类AbstractValidateAttribute,在新增的类中实现具体的校验逻辑,并且在需要校验的属性上标记对应的特性即可,方便代码扩展。

特性的应用

特性实现枚举展示描述信息

(1)创建枚举描述特性RemarkAttribute

  [AttributeUsage(AttributeTargets.Field)]
    public class RemarkAttribute:Attribute
    {
        public string Remark { get; set; }
        public RemarkAttribute(string remark)
        {
            this.Remark = remark;
        }
    }

(2)创建枚举(有三个状态:正常,冻结,删除),并进行标记

  public enum UserState
    {
        //正常状态
        [Remark("正常状态")]
        Normal =0,
        //冻结状态
        [Remark("冻结状态")]
        Frozen =1,
        //删除状态
        [Remark("删除状态")]
        Deleted = 2

    }

(3)通过反射调用特性

    public static class AttributeExtend
    {
        public static string GetRemark(this Enum value)
        {
            Type type = value.GetType();
            var field = type.GetField(value.ToString());
            if (field.IsDefined(typeof(RemarkAttribute), true))
            {
                RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true);
                return attribute.Remark;
            }
            else
            {
                return value.ToString();
            }
        }
    }

(4)主程序调用

       static void Main(string[] args)
        {
           UserState userState = UserState.Frozen;
           string reamrk = userState.GetRemark();
           Console.WriteLine(reamrk);
        }
//打印结果:“冻结状态”

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

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

相关文章

基于spss的多元统计分析 之 聚类分析+判别分析(2/8)

实验目的&#xff1a; 1&#xff0e;掌握聚类分析及判别分析的基本原理&#xff1b; 2&#xff0e;熟悉掌握SPSS软件进行聚类分析及判别分析的基本操作&#xff1b; 3&#xff0e;利用实验指导的实例数据&#xff0c;上机熟悉聚类分析及判别分析方法。 实验前预习&#xff1a;…

js中原型和原型链的理解(透彻)

js中原型、原型链、继承的理解&#xff08;透彻&#xff09; 1、前言1.1 什么是函数对象1.2 什么是实例对象1.3 什么是原型对象1.4 构造函数、原型对象、实例对象的关系 2、原型3、原型链4、原型的相关属性及方法5、总结 1、前言 1.1 什么是函数对象 函数对象就是我们平时称呼…

centos连接XShell

先设置网络自动连接&#xff0c;为Xshell 连接centos做准备 选择应用程序->系统工具->设置 选择网络&#xff0c;如果有线没有打开&#xff0c;选择打开&#xff0c;在点击设置 记住ipv4地址&#xff0c;选择自动连接&#xff0c;然后应用 最后鼠标右键点击桌面&#xf…

RabbitMQ入门案例之Topic模式

前言&#xff1a; 本文章将介绍RabbitMQ中的Topic&#xff08;主题&#xff09;模式&#xff0c;其中还会涉及 ‘#’ 和 ‘*’ 两个通配符在RabbitMQ中的区别。 官网文档地址&#xff1a;https://rabbitmq.com/getstarted.html 什么是Topic模式 RabbitMQ的Topic模式是一种基于…

SpringBoot 如何使用 Spring Integration 处理事件

SpringBoot 如何使用 Spring Integration 处理事件 Spring Integration 是 Spring Framework 的一个扩展&#xff0c;它提供了一种基于消息传递的集成模式。使用 Spring Integration&#xff0c;我们可以将不同的应用程序、系统和服务连接起来&#xff0c;从而实现数据的传递、…

VMware中Linux虚拟机配置静态ip

一、输入ip addr查看ip地址 二、输入cd /etc/sysconfig/network-scripts进入centos网络配置文件夹 三、接着输入ls查看目录 四、 输入vi ifcfg-ens33进入网卡配置 五、 进入以后是这个界面&#xff0c;红色方框里的内容是需要手动修改的&#xff0c;下面图片里已经修改过了。 …

【C】分支和循环语句的简单介绍

语句 分支语句if语句语法结构代码演示 switch语句语法结构代码演示 循环语句while循环语法结构代码分析 for循环语法结构代码演示 do...while循环语法结构代码分析 什么是语句呢&#xff1f; 在C语言中由分号&#xff08;;&#xff09;隔开的就是一条语句。 分支语句 if语句 …

【算法设计与分析】期末考试知识总结(知识超浓缩版)

目录 简要介绍 复杂度 迭代 插入排序 二分查找 快排划分 选择排序 计数排序 基数排序 桶排序 递归 递归式的计算-四种方法 欧几里得算法 汉诺塔问题 快速排序 归并排序 堆排序 分治 二维极大点问题 一维最邻近点对 二维最邻近点对 逆序对的数目 凸包 最大字段…

RecyclerView 低耦合单选、多选模块实现

作者&#xff1a;丨小夕 前言 需求很简单也很常见&#xff0c;比如有一个数据列表RecyclerView&#xff0c;需要用户去点击选择一个或多个数据。 实现单选的时候往往简单下标记录了事&#xff0c;实现多选的时候就稍微复杂去处理集合和选中。随着项目选中需求增多&#xff0c…

k8s的部署

二进制搭建 Kubernetes v1.20 k8s集群master01&#xff1a;192.168.92.30 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集群master02&#xff1a;192.168.92.21 k8s集群node01&#xff1a;192.168.92.40 kubelet kube-proxy docker k8s集群node02…

阿里云热修复打补丁包注意事件

1、每次发布app到应用市场前&#xff0c;注意保存没有加固前的apk文件和mapping.txt 2、修复好bug&#xff0c;打包app前&#xff0c;要做的事情 &#xff08;1)先把有问题的apk的mapping.txt文件复制到/app路径下 (2)修改混淆配置&#xff1a;将-printmapping mapping.txt使…

Android蓝牙协议知识汇总

蓝牙协议下载 蓝牙技术联盟网址&#xff1a;https://www.bluetooth.com/ 在这个网址搜索&#xff0c;比如&#xff1a; 在搜索结果中找到蓝牙协议规范&#xff1a; 点击上面网址&#xff1a; 蓝牙手册里包含了部分核心协议&#xff0c;比如L2CAP、SDP、ATT、GATT&#x…

Python 100%解析svg-captcha验证码

前言 前段时间接到一个需求&#xff0c;登陆某一个网站&#xff0c;然后录入数据&#xff1b;本来以为是一个很简单的需求&#xff0c;结果遇到几个难点&#xff1a; 登陆的时候需要有验证码验证码是一个请求路径&#xff0c;每请求一次验证码都不一样 本来一开始以为是常用的…

探究 CoreData 使用索引(Index)机制加速查表究竟如何实现?

问题现象 在  App 的开发中,CoreData 到底能不能用索引机制(Index)来加速查表?如果可以,又该如何创建和使用索引呢? 这是一个连  官方文档都模棱两可,Stackoverflow 里诸多大神都闪烁其词的话题。 在本篇博文中,您将学到如下内容: 什么是 CoreData 索引(Index…

SpringBoot + Ant Design Vue实现数据导出功能

SpringBoot Ant Design Vue实现数据导出功能 一、需求二、前端代码实现2.1 显示实现2.2 代码逻辑 三、后端代码实现3.1 实体类3.2 接收参数和打印模板3.3 正式的逻辑3.4 Contorller 一、需求 以xlsx格式导出所选表格中的内容要求进行分级设置表头颜色。 二、前端代码实现 2…

20230524 taro+vue3+webpack5+pdfjs时打包pdfjs进不来的问题

关闭taro的terser就可以了 terser:{enable:false }

UE中创建异步任务编辑器工具(Editor Utility Tasks)

在UE中我们往往需要执行一些编辑器下的异步任务&#xff0c;例如批量生成AO贴图、批量合并静态模型等&#xff0c;又不想阻碍主线程&#xff0c;因此可以使用Editor Utility Tasks直接创建UE编辑器下的异步任务。 如果你不太了解UE编辑器工具&#xff0c;可以参考这篇文章&…

Spring Boot 中自定义数据校验注解

Spring Boot 中自定义数据校验注解 在 Spring Boot 中&#xff0c;我们可以使用 JSR-303 数据校验规范来校验表单数据的合法性。JSR-303 提供了一些常用的数据校验注解&#xff0c;例如 NotNull、NotBlank、Size 等。但是&#xff0c;在实际开发中&#xff0c;我们可能需要自定…

2023年6月24日(星期六):骑行明郎

2023年6月24日(星期六)&#xff1a;骑行明郎&#xff0c;早8:30到9:00&#xff0c; 大观公园门囗集合&#xff0c;9:30点准时出发 【因迟到者&#xff0c;骑行速度快者&#xff0c;可自行追赶偶遇。】 偶遇地点: 大观公园门囗集合&#xff0c;家住南&#xff0c;东&#xff0c…

(二叉树) 100. 相同的树 ——【Leetcode每日一题】

❓100. 相同的树 难度&#xff1a;简单 给你两棵二叉树的根节点 p 和 q&#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q …