【从零开始入门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配合使用,…...

【偏好对齐】通过ORM直接推导出PRM
论文地址:https://arxiv.org/pdf/2412.01981 相关博客 【自然语言处理】【大模型】 ΨPO:一个理解人类偏好学习的统一理论框架 【强化学习】PPO:近端策略优化算法 【偏好对齐】PRM应该奖励单个步骤的正确性吗? 【偏好对齐】通过OR…...

Python与其他编程语言的区别是什么?
Python是一种广泛使用的高级编程语言,以其简洁的语法、强大的库支持和广泛的应用领域而著称。与其他编程语言相比,Python具有许多独特的特点和优势。以下将从多个方面详细探讨Python与其他编程语言的区别,并通过示例进行说明。 一、语法简洁…...

cuda11.6和对应的cudnn(windows)
因为每次不同的torch版本要下对应的cuda,这次刚好在Windows上下好了一个cuda11.6和对应的cudnn,直接放到网盘中,大家有需要对应版本的可以直接下载: 链接:https://pan.quark.cn/s/f153a53830d4 大家自取,c…...

24年无人机行业资讯 | 12.23-12.29
24年无人机行业资讯 | 12.23-12.29 1、 国家发改委新设低空经济司,助力低空经济规范发展2、商务部支持无人机民用国际贸易,强调出口管制与安全并重3、滨州高新区首架无人机成功下线4、 2025第九届世界无人机大会筹备推进会顺利召开5、2024年世界无人机竞…...

uniapp:微信小程序文本长按无法出现复制菜单
一、问题描述 在集成腾讯TUI后,为了能让聊天文本可以复制,对消息组件的样式进行修改,主要是移除下面的user-select属性限制: user-select: none;-webkit-user-select: none;-khtml-user-select: none;-moz-user-select: none;-ms…...

qml Item详解
1、概述 Item是QML(Qt Modeling Language)的基础元素,所有其他可视化元素都继承自它。它代表了一个可视化的对象,虽然Item对象本身没有可视外观,但它定义了所有可视项之间通用的属性,比如位置、大小、旋转…...

【Java回顾】Day4 反射机制
反射机制 之前学过一部分,笔记在20250103Java包_网络编程.md里,这里在之前的笔记的基础上做一些补充。 反射:得到class对象后反向获取对象的各种信息。 包 Field 类或接口中的字段(成员变量),动态访问和修改类的字段 模板 获取Class 对象 …...

【沉默的羔羊心理学】汉尼拔的“移情”游戏:操纵与理解的艺术,精神分析学视角下的角色互动
终极解读《沉默的羔羊》:弗洛伊德精神分析学视角下的深层剖析 关键词 沉默的羔羊弗洛伊德精神分析学角色心理意识与潜意识性别与身份 弗洛伊德精神分析学简介 弗洛伊德的精神分析学是心理学的一个重要分支,主要关注人类行为背后的无意识动机和冲突。…...

[深度学习] 大模型学习1-大语言模型基础知识
大语言模型(Large Language Model,LLM)是一类基于Transformer架构的深度学习模型,主要用于处理与自然语言相关的各种任务。简单来说,当用户输入文本时,模型会生成相应的回复或结果。它能够完成许多任务&…...

如何解决数据库和缓存不一致的问题
目录 一、Cache-Aside模式(旁路缓存模式) 二、Write-Through模式(写透缓存模式) 三、Write-Behind模式(写回缓存模式) 四、先删除缓存再更新数据库(不推荐,存在风险)…...