C# 特性详解

news2024/10/7 10:13:28

目录

特性是什么?

如何使用特性?

(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/663116.html

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

相关文章

划重点!超全PMP报考指南,速速收藏!

PMP证书作为项目管理专业人士的重要标志&#xff0c;是每一位项目经理职业发展生涯中不可或缺的证书。PMP学习不仅可以提升项目经理的项目管理水平&#xff0c;也可以进一步提升项目经理的个人竞争力。 一&#xff0c;PMP介绍 PMP&#xff08;项目管理专业人士资格认证&#…

新建一个vue2项目

安装Vue CLI 在终端中输入以下命令进行全局安装&#xff1a; npm install -g vue/cli 创建Vue项目 1.在你要创建项目的目录下打开终端输入vue create 【你的项目名称】 2.手动选择安装 3.根据自己的需求选择 4.选择vue版本 5.是否使用calss风格的组件语法&#xff1a; (推荐…

8.1 什么是互联网

目录 计算机网络的定义与分类 计算机网路的定义 几种不同类别的计算机网络 按照网络的作用范围进行分类 按照网络的使用者进行分类 网络的网络 计算机网络 互连网络 网络与互连网 互联网 互联网的组成 互联网的边缘部分 互联网基础结构发展的三个阶段 第一阶段&a…

JavaWeb小记——Servlet

目录 Servlet概念 Servlet快速入门 1.创建web项目 2.创建一个Java类&#xff0c;实现Servlet接口 3.重写接口中的service()方法 4.配置Servlet——web.xml中 5.发布项目 6.发送请求&#xff0c;请求Servlet Servlet原理 Servlet的生命周期 1.init() 2.service() 3.…

el-form 表单项前面显示星号 但又不校验这一项代码怎么实现

效果图&#xff1a; 1.将 prop 属性设置为空字符串&#xff1b; 2.同时将 required 属性设置为 true。 这样就可以显示星号但不进行校验。 示例代码&#xff1a; <el-form> <el-form-item label"姓名" prop"name" required> <el-input…

SAP ERP系统PP模块控制BOM的变更是否需要ECN的三种解决方法<转载>

原文链接&#xff1a;http://www.360doc.com/content/12/0121/07/1079063886_1079063886.shtml BOM简称物料清单&#xff0c;它是PC和MC部门编制生产计划&#xff0c;物料采购和生产计划的依据&#xff0c;也是财务成本核算的基础&#xff0c;它的重要性不言而喻。 一个公司的…

Go语言精进之路读书笔记—第二章 项目结构、代码风格与标识符命名

上面是go语言库项目结构&#xff0c;细节补充如下&#xff1a; cmd目录&#xff1a; 存放项目要构建的可执行文件对应的main包源文件。 pkg目录&#xff1a; 项目自身要使用并且同样也是可执行文件对应main包要依赖的库文件。 makefile&#xff1a; 代码任何第三方构建工具脚本…

设备算法加密授权“安全+易用=?”

一提到软件授权&#xff0c;大家的第一反应都是“安全”和“易用”的问题&#xff0c;尤其是在算法加密授权领域。软件企业想要十足的安全性用来防止反编译&#xff0c;同时又需要具备灵活和易用来促使企业商业模式落地。那现在市面上是否有这样一款兼顾“安全性”和“易用性”…

谈谈:File、Blob、FileReader、ArrayBuffer、base64

JavaScript 提供了一些 API 来处理文件或原始文件数据&#xff0c;例如&#xff1a;File、Blob、FileReader、ArrayBuffer、base64 等。下面就来看看它们都是如何使用的&#xff0c;它们之间又有何区别和联系。 1. Blob Blob全称为binary large Object 即二进制大对象&#x…

嵌入式软件开发工程师具体可以分三类

嵌入式软件开发工程师可以根据其职责和专长分为以下三类&#xff1a;底层固件开发工程师&#xff1a;底层固件开发工程师负责编写和优化嵌入式系统的底层软件&#xff0c;与硬件密切相关。他们通常需要熟悉处理器架构、寄存器级编程、中断处理、设备驱动程序和实时操作系统&…

Oracle21C + PLSQL Developer 15 + Oracle客户端21安装配置完整图文版

一、Oracle21C PLSQL Developer 15 Oracle客户端文件下载 1、Oracl21C下载地址&#xff1a;https://www.oracle.com/cn/database/technologies/oracle-database-software-downloads.html 2、 PLSQL Developer 15下载地址&#xff1a;https://www.allroundautomations.com/re…

微信支付(JSAPI支付)/支付宝支付(手机网站支付)实现思路及实现方案-无源码

背景 停车系统 一个二维码同时支持微信及支付宝扫码付款&#xff0c;使用手机网站实现 临时车费用缴费二维码需要通知支持微信及支付宝。用户缴纳的停车费直接到各个商户的账户上&#xff0c;不经过停车系统开发公司。 方案 微信&#xff1a;使用微信服务商提供的服务支付…

【twcc】学习2:cc-feedback包送去cc预估码率

继续学习1,学习1中是准备知识,实际操练是在本文的预估中。 主要是对照大神的神作第八章 学习。 大量引用了大神的内容。 学习1中,大神主要论述了发送侧如何构造cc-fb,等待收到rtcp-cc-fb后进行再更新,然后最终交给cc模块。 这是大神绘制的图片,总结的非常清晰到位: 大神…

归一化详细推导

1. 一组数减去平均数的差的和为0。 一组数:a1,a2,a3,……,an, 平均数:a=(a1+a2+……+an)/n, 则 a1+a2+……+an=n*a, 从而,每一个数减去平均数的差的和为 (a1-a)+(a2-a)+……+(an-a) =(a1+a2+……+an)-n*a =0 2. 设原始数据均值及标准差为,将原始数组经过变换后得到使得均…

保护您的网站免受黑客攻击的七个良好习惯

世界上通常有三种类型的网站/APP拥有者&#xff1a;第一种是一直具备较高风险意识的人、第二种是非常自信认为没有任何东西可以攻击和破坏他们网站的人&#xff0c;第三种&#xff1a;根本不关心它的人。 第二种远比另外两种人更容易感受后悔和痛苦。实际上&#xff0c;黑客攻…

autocad中的快文件

一、块的定义 图块也称块&#xff0c;它是由一组图形对象组成的集合&#xff0c;一组对象一旦被定义为图块&#xff0c;它们将成 为一个整体&#xff0c;选中图块中任意一个图形对象即可选中构成图块的所有对象。AutoCAD 把一个 图块作为一个对象进行编辑修改等操作&…

docker常用基本命令及安装

docker常用基本命令及安装 1. docker启动等命令2. 常用基本操作命令3. 删除镜像3.1 步骤如下&#xff1a;3.2 可能会出现的问题 4. 宿主机与容器之间拷贝文件5. 关于docker的安装5.1 安装5.2 解决拉取镜像失败&#xff08;超时&#xff09;——配置 docker 镜像加速 1. docker启…

有什么好用的mp3转wav软件?分享这几个方法!

无论是在日常生活还是工作中&#xff0c;我们经常需要进行音频格式转换&#xff0c;如将MP3转换为WAV。通常情况下&#xff0c;为了解决这个问题&#xff0c;我们需要依赖专业的音频转换工具。下面介绍三款非常好用的音频转换软件&#xff0c;供参考&#xff1a; 一、记灵在线…

Git(1)

文章目录 1. 初始 Git2. 安装 Git3. 创建仓库4. 新增配置项5. 认识 Git 工作区 &#xff0c; 缓存区 &#xff0c; 版本库6. 添加文件7. 查看 .git 文件8. 修改文件9. 版本回退10. 撤销修改11 . 总结 1. 初始 Git 提出问题 : 图一 : 图二 : 图三 : 2. 安装 Git 3. 创建仓库 只…

一个资深测试工程师面试一来就问我这些题目

作为一个已经工作有10年经验的测试工程师&#xff0c;其间也辗转了几个大的互联网公司&#xff0c;虽然确实缺少了一些稳定性&#xff0c;但同时也积累了一些面试的经验&#xff0c;不才分享一些给大家。那么主要是针对测试工程师的一些总结&#xff0c;对于其他的工种&#xf…