【从零开始入门unity游戏开发之——C#篇40】C#特性(Attributes)和自定义特性
文章目录
- 前言
- 一、特性(`Attributes`)基本概念
- 二、自定义特性
- 1、自定义特性代码示例:
- 2、应用自定义特性:
- 3、解释
- 3.1 **AttributeUsage 特性**
- 3.2 特性的命名
- 3.3 **构造函数**:
- 3.4 **属性**:
- 4、使用反射获取特性信息
- 5、内置的系统特性
- 5.1 **`[AttributeUsage]`**
- 5.2 **`[Obsolete]`**
- 5.3 **`[Serializable]`**
- 5.4 **`[Conditional]`**
- 5.5 **`[DllImport]`**
- 5.6 **`[DebuggerDisplay]`**
- 5.7`[Flags]`
- 6、总结
- 专栏推荐
- 完结
前言
特性本身是通过类实现的,通常用于描述代码的某些方面(例如标记、验证、代码生成等),并可以在运行时通过反射获取。
一、特性(Attributes)基本概念
在 C# 中,特性 是一种附加信息或元数据,允许开发者向程序集、类、方法、属性、字段等添加自定义标记或信息。用于在代码中嵌入描述信息,通常用于描述代码结构(如类、方法、属性等)的附加信息。这些元数据在编译时嵌入程序集,并可以在运行时通过反射获取。
- 本质:特性本质上是一个类,继承自
System.Attribute类。 - 用途:可以将特性应用于类、方法、属性、字段、参数等,作为额外的元数据或标记。
- 反射:特性信息通常在运行时通过
反射来访问。
二、自定义特性
为了定义一个特性,必须创建一个继承自 System.Attribute 的类。可以通过构造函数或者字段来传递数据。
1、自定义特性代码示例:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class MyCustomAttribute : Attribute
{public string Info { get; }// 构造函数public MyCustomAttribute(string info){Info = info;}// 可以在特性中定义方法public void DisplayInfo(){Console.WriteLine(Info);}
}
2、应用自定义特性:
[MyCustom("这是类的特性")]
public class MyClass
{[MyCustom("这是成员变量的特性")]public int Value;[MyCustom("这是方法的特性")]public void TestMethod(){Console.WriteLine("执行方法");}
}
3、解释
3.1 AttributeUsage 特性
-
AttributeTargets:这是一个必填参数,类型为AttributeTargets枚举的组合,指定了该特性可以应用于哪些程序元素。- All:所有可能的目标。
- Assembly:程序集级别。
- Class:类级别。
- Constructor:构造函数级别。
- Delegate:委托级别。
- Enum:枚举级别。
- Event:事件级别。
- Field:字段级别。
- Interface:接口级别。
- Method:方法级别。
- Module:模块级别(通常是指编译单元)。
- Parameter:参数级别。
- Property:属性级别。
- ReturnValue:返回值级别(用于特性应用于方法返回值)。
- Struct:结构体级别。
你可以通过按位或运算符 (
|) 来组合多个目标。例如:[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class MyCustomAttribute : Attribute { }这表示 MyCustomAttribute 可以应用于
类和方法。 -
AllowMultiple:这是一个可选布尔参数,默认值为 false。如果设置为 true,则允许多个相同类型的特性应用于同一个程序元素。[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class MultiUseAttribute : Attribute { }在这种情况下,你可以多次应用 MultiUseAttribute 到同一个类上
[MultiUse("First use")] [MultiUse("Second use")] public class MyClass { } -
Inherited:这也是一个可选布尔参数,默认值为 false。如果设置为 true,则子类会继承父类上的该特性;否则,子类不会继承这些特性。[AttributeUsage(AttributeTargets.Class, Inherited = true)] public class InheritableAttribute : Attribute { }在这种情况下,如果 DerivedClass 继承了 BaseClass,并且 BaseClass 上有 InheritableAttribute,那么 DerivedClass 也会被认为具有该特性:
[Inheritable] public class BaseClass { }public class DerivedClass : BaseClass { }
3.2 特性的命名
在C#中,特性类名通常带有 Attribute 后缀,但在应用时可以省略。例如:
public class MyCustomAttribute : Attribute { }
然而,在实际应用这个特性时,你可以选择省略 Attribute 后缀。编译器会自动识别并匹配到相应的特性类。因此,以下两种写法是等价的:
[MyCustom("这是一个测试")]
public class MyClass { }[MyCustomAttribute("这是一个测试")]
public class MyClass { }
这种灵活性使得特性名称更加简洁,尤其是在频繁使用的情况下。不过,为了保持代码的一致性和可读性,建议在定义特性时始终使用完整的 Attribute 后缀,而在应用时可以选择省略。
3.3 构造函数:
构造函数用于初始化特性实例,并接受参数以传递给特性。通过构造函数,你可以在应用特性时提供必要的信息。构造函数可以包含任意数量的参数,但通常应该尽量保持简单,以便易于使用。
示例:带参数的构造函数
public class MyCustomAttribute : Attribute
{public string Description { get; }// 构造函数接受一个字符串参数,并将其赋值给Description属性public MyCustomAttribute(string description){Description = description;}
}
在这个例子中,当你应用 MyCustom 特性时,必须提供一个字符串参数:
[MyCustom("这是一个测试类")]
public class MyClass { }
如果你需要传递多个参数,可以在构造函数中添加更多的参数:
public class MyCustomAttribute : Attribute
{public string Description { get; }public int Priority { get; }public MyCustomAttribute(string description, int priority){Description = description;Priority = priority;}
}// 应用特性时传递两个参数
[MyCustom("这是一个测试类", priority: 1)]
public class MyClass { }
3.4 属性:
属性用于存储特性接收到的数据,并可以在运行时通过反射访问这些数据。你可以定义任意数量的公共属性来保存不同类型的配置信息。
示例:定义和使用属性
public class MyCustomAttribute : Attribute
{public string Description { get; }public int Priority { get; }// 构造函数初始化属性public MyCustomAttribute(string description, int priority){Description = description;Priority = priority;}// 可选:定义只读或读写属性public bool IsEnabled { get; set; } = true;
}// 应用特性时设置属性
[MyCustom("这是一个测试类", priority: 1, IsEnabled = false)]
public class MyClass { }
注意,对于非构造函数参数的属性(如 IsEnabled),你需要在应用特性时使用命名参数的形式来设置它们。
4、使用反射获取特性信息
你可以通过反射来访问类型或成员上应用的特性。在运行时,可以使用 Attribute.GetCustomAttributes 或 MemberInfo.GetCustomAttributes 方法来检索已应用的特性。
获取特性信息代码示例:
using System;
using System.Reflection;[AttributeUsage(AttributeTargets.Method)]
public class CustomMethodAttribute : Attribute
{public string Info { get; }public CustomMethodAttribute(string info){Info = info;}
}public class MyClass
{[CustomMethodAttribute("This is a custom method")]public void MyMethod(){Console.WriteLine("MyMethod executed.");}
}class Program
{static void Main(){// 获取 MyClass 类型上的所有方法MethodInfo methodInfo = typeof(MyClass).GetMethod("MyMethod");// 获取 MyMethod 上的 CustomMethodAttribute 特性CustomMethodAttribute attribute = (CustomMethodAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(CustomMethodAttribute));if (attribute != null){Console.WriteLine(attribute.Info); // 输出: This is a custom method}}
}
5、内置的系统特性
C# 提供了许多内置的系统特性(Attributes),这些特性可以帮助开发者更高效地编写代码,控制编译器行为,以及与各种框架和工具进行交互。以下是C#中一些常用的系统内置特性及其应用场景。
5.1 [AttributeUsage]
前介绍的AttributeUsage其实就是一个内置的系统特性,用于定义自定义特性的应用范围和其他行为。前面已经详细介绍过这个特性。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyCustomAttribute : Attribute { }
5.2 [Obsolete]
用于标记过时的方法、类或其他成员,告知其他开发者不要使用它们,并可以在必要时引发编译警告或错误。
//第一个参数是提示信息
//第2个参数可选,默认false,表示警告, true 表示这会导致编译错误
[Obsolete("请使用新方法代替", true)]
public void OldMethod() { }
效果

5.3 [Serializable]
指示类可以序列化,意味着该类的实例可以转换为字节流以便存储或传输。
[Serializable]
public class Person
{public string Name { get; set; }public int Age { get; set; }
}
解释:如果你希望把一个类的对象保存到文件或者通过网络发送出去,就需要用到这个特性。它告诉程序:“我可以被转换成数据流”。
5.4 [Conditional]
指定条件编译符号,只有当符号定义时才会编译相应的方法调用。常用于调试信息记录。
[Conditional("DEBUG")]
public void DebugLog(string message)
{Console.WriteLine(message);
}
解释:当你只在某些条件下才想执行某个方法时(比如调试模式下记录日志),可以用这个特性。它告诉编译器只有当条件满足时才包含这段代码。
[Conditional] 特性和前面我们介绍得预编译指令 #if, #endif实现效果类似,翻译成预编译指令如下:
#if DEBUG
public void DebugLog(string message)
{Console.WriteLine(message);
}
#endif
当你在方法上应用 [Conditional] 特性时,编译器会根据你提供的条件符号来决定是否包含对该方法的调用。如果定义了该符号(这里是DEBUG符合),则保留调用;否则,编译器会忽略这些调用,就好像它们不存在一样。
5.5 [DllImport]
主要用于调用外部库。
using System.Runtime.InteropServices;[DllImport("user32.dll", SetLastError = true)]
static extern bool SetCursorPos(int X, int Y);
解释:有时候你需要调用一些不是用C#写的函数(比如Windows系统的功能),这时就可以用这个特性。它告诉程序去哪里找这些函数。
之前开发桌面宠物时有时使用过很多,感兴趣可以看看:
【制作100个unity游戏之32】unity开发属于自己的一个2d/3d桌面宠物,可以实时计算已经获取的工资
5.6 [DebuggerDisplay]
用于自定义调试器显示的信息,使得在调试时更容易查看对象的状态。
[DebuggerDisplay("Name = {Name}, Age = {Age}")]
public class Person
{public string Name { get; set; }public int Age { get; set; }
}
比如,实际其实没啥用

5.7[Flags]
当你在枚举上应用 [Flags] 特性时,它告诉编译器和开发者这个枚举的值可以按位组合(即通过按位或运算 | 组合多个值)。这样,你可以用一个单一的变量来表示多个选择,而不是为每个选择创建单独的变量。
[Flags]
public enum FileAccess
{Read = 1,Write = 2,ReadWrite = Read | Write
}
在这个例子中,FileAccess 枚举的值被设计成可以组合使用。例如,ReadWrite 是由 Read 和 Write 组合而成。
6、总结
学习 C# 自定义特性的意义在于它能够极大地增强代码的灵活性、可扩展性以及可读性。通过自定义特性(Attributes),开发者可以在代码中添加元数据,并通过反射等机制动态地处理这些元数据,进而实现一些通用的功能和行为。这种技术在大型系统、框架和库中尤为重要。
可能在初期看不出太多具体的应用场景,但随着后面对 Unity 引擎的深入了解,你会发现自定义特性在项目中的强大作用。在 Unity 中,也自定义了很多默认的特性,很大程度上增强编辑器和游戏逻辑的灵活性和可扩展性,这个知识就留到【unity篇】再讲解了。
专栏推荐
| 地址 |
|---|
| 【从零开始入门unity游戏开发之——C#篇】 |
| 【从零开始入门unity游戏开发之——unity篇】 |
| 【制作100个Unity游戏】 |
| 【推荐100个unity插件】 |
| 【实现100个unity特效】 |
| 【unity框架开发】 |
完结
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!
好了,我是向宇,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

相关文章:
【从零开始入门unity游戏开发之——C#篇40】C#特性(Attributes)和自定义特性
文章目录 前言一、特性(Attributes)基本概念二、自定义特性1、自定义特性代码示例:2、应用自定义特性:3、解释3.1 **AttributeUsage 特性**3.2 特性的命名3.3 **构造函数**:3.4 **属性**: 4、使用反射获取特…...
DES密码的安全性分析(简化版本)
DES仍是世界上使用最广的(DES发行后20年,互联网的兴起,人们开始觉得DES不安全了,但DES的实现成本也越来越低) 宏观分析: 密钥空间方面: 密钥长度:DES 算法使用 56 位的密钥对数据…...
引入三方jar包命令
mvn install:install-file \ -Dfile本地磁盘路径 \ -DgroupId组织名称 \ -DartifactId项目名称 \ -Dversion版本号 \ -Dpackagingjar 例如 假设你的 JAR 文件路径是 /home/user/common-pojo-1.0-SNAPSHOT.jar,组织名称是 com.example,项目名…...
机器学习基础-机器学习的常用学习方法
半监督学习的概念 少量有标签样本和大量有标签样本进行学习;这种方法旨在利用未标注数据中的结构信息来提高模型性能,尤其是在标注数据获取成本高昂或困难的情况下。 规则学习的概念 基本概念 机器学习里的规则 若......则...... 解释:如果…...
在控制领域中如何区分有效性、优越性、稳定性和鲁棒性?
在控制领域中,区分有效性、优越性、稳定性和鲁棒性可以通过具体的控制器设计实例来更好地理解。以下以经典的质量-弹簧-阻尼系统的PID控制器设计为例,展示如何区分这四个性能指标。 经典质量-弹簧-阻尼系统的PID控制器设计 质量-弹簧-阻尼系统模型 考…...
美国宏观经济基础框架梳理
玩转币圈和美股,最关键的是理解美国宏观经济。以下是核心逻辑:美国经济数据→政策调整→资金流动→资产价格变化。掌握这些因素的关系,才能在市场中立于不败之地。 一、核心变量及其意义 1. GDP(国内生产总值) • …...
装饰器模式详解
装饰器模式(Decorator Pattern)是一种设计模式,属于结构型模式之一。它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有类的一个实例,从而扩展该实例的功能。…...
[最新] SIM卡取出后还能找到我的iPhone吗?
您是否曾在任何地方丢失过 SIM 卡?或者您是否已移除 SIM 卡,现在无法在任何地方找到您的 iPhone?在这篇博客中,您将了解即使 SIM 卡被移除,“查找我的 iPhone”也能正常工作。 在某些情况下,您必须取出 SIM…...
数据分析思维(六):分析方法——相关分析方法
数据分析并非只是简单的数据分析工具三板斧——Excel、SQL、Python,更重要的是数据分析思维。没有数据分析思维和业务知识,就算拿到一堆数据,也不知道如何下手。 推荐书本《数据分析思维——分析方法和业务知识》,本文内容就是提取…...
谷歌2025年AI战略与产品线布局
在2024年12月的战略会议上,谷歌高层向员工描绘了2025年的宏伟蓝图,特别是在人工智能(AI)领域。这一年被定位为AI发展的关键转折点,谷歌计划通过一系列新产品和创新来巩固其在全球科技领域的领导地位。本文将深入探讨谷歌的2025年AI战略、重点产品以及竞争策略。 一、整体…...
登录的几种方式
使用Session完成登录 1. 手机号发送验证码 逻辑步骤: 校验手机号格式是否正确。生成验证码(例如使用Hutool工具类)。将手机号和验证码存入Session。返回验证码发送成功的响应。 2. 用户登录逻辑 逻辑步骤: 从Session中获取存…...
Scala_【5】函数式编程
第五章 函数式编程函数和方法的区别函数声明函数参数可变参数参数默认值 函数至简原则匿名函数高阶函数函数作为值传递函数作为参数传递函数作为返回值 函数闭包&柯里化函数递归控制抽象惰性函数友情链接 函数式编程 面向对象编程 解决问题时,分解对象ÿ…...
解析 World Football Cup 问题及其 Python 实现
问题描述 本文讨论一道关于足球锦标赛排名规则的问题,来自 Berland 足球协会对世界足球规则的调整。题目要求对给定的比赛数据进行计算,并输出能进入淘汰赛阶段的球队列表。以下是规则详细描述。 题目规则 输入格式: 第一行包含一个整数 …...
9.系统学习-卷积神经网络
9.系统学习-卷积神经网络 简介输入层卷积层感受野池化层全连接层代码实现 简介 卷积神经网络是一种用来处理局部和整体相关性的计算网络结构,被应用在图像识别、自然语言处理甚至是语音识别领域,因为图像数据具有显著的局部与整体关系,其在图…...
基于FPGA的出租车里程时间计费器
基于FPGA的出租车里程时间计费器 功能描述一、系统框图二、verilog代码里程增加模块时间增加模块计算价格模块上板视频演示 总结 功能描述 (1);里程计费功能:3公里以内起步价8元,超过3公里后每公里2元,其中…...
三甲医院等级评审八维数据分析应用(五)--数据集成与共享篇
一、引言 1.1 研究背景与意义 随着医疗卫生体制改革的不断深化以及信息技术的飞速发展,三甲医院评审作为衡量医院综合实力与服务水平的重要标准,对数据集成与共享提出了更为严苛的要求。在传统医疗模式下,医院内部各业务系统往往各自为政,形成诸多“信息孤岛”,使得数据…...
VUE条件树查询 自定义条件节点
之前实现过的简单的条件树功能如下图: 经过最新客户需求确认,上述条件树还需要再次改造,以满足正常需要! 最新暴改后的功能如下红框所示: 页面功能 主页面逻辑代码: <template><div class"…...
什么是打流,怎么用iperf3打流
什么是打流 在网络安全和黑灰产领域,“打流”具有不同的含义,常用于形容通过技术手段制造流量假象或发起流量攻击。 流量攻击(DDoS)中的“打流”: “打流”指向目标服务器或网络发起 大规模的数据请求,造…...
使用MySQL APT源在Linux上安装MySQL
全新安装MySQL的步骤 以下说明假定您的系统上尚未安装任何版本的MySQL(无论是由Oracle还是其他方分发) 添加MySQL的Apt源。 将MySQL的APT存储库添加到系统的软件存储库列表中。 1、转到MySQL APT存储库的下载页面MySQL :: Download MySQL APT Reposi…...
redux react-redux @reduxjs/toolkit
redux团队先后推出了redux、react-redux、reduxjs/toolkit,这三个库的api各有不同。本篇文章就来梳理一下当我们需要在项目中集成redux,从直接使用redux,到使用react-redux,再到react-redux和reduxjs/toolkit配合使用,…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...
网页端 js 读取发票里的二维码信息(图片和PDF格式)
起因 为了实现在报销流程中,发票不能重用的限制,发票上传后,希望能读出发票号,并记录发票号已用,下次不再可用于报销。 基于上面的需求,研究了OCR 的方式和读PDF的方式,实际是可行的ÿ…...
STL 2迭代器
文章目录 1.迭代器2.输入迭代器3.输出迭代器1.插入迭代器 4.前向迭代器5.双向迭代器6.随机访问迭代器7.不同容器返回的迭代器类型1.输入 / 输出迭代器2.前向迭代器3.双向迭代器4.随机访问迭代器5.特殊迭代器适配器6.为什么 unordered_set 只提供前向迭代器? 1.迭代器…...
