【从零开始入门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.GetCustomAttributesMemberInfo.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 是由 ReadWrite 组合而成。

6、总结

学习 C# 自定义特性的意义在于它能够极大地增强代码的灵活性、可扩展性以及可读性。通过自定义特性(Attributes),开发者可以在代码中添加元数据,并通过反射等机制动态地处理这些元数据,进而实现一些通用的功能和行为。这种技术在大型系统、框架和库中尤为重要。

可能在初期看不出太多具体的应用场景,但随着后面对 Unity 引擎的深入了解,你会发现自定义特性在项目中的强大作用。在 Unity 中,也自定义了很多默认的特性,很大程度上增强编辑器和游戏逻辑的灵活性和可扩展性,这个知识就留到【unity篇】再讲解了。


专栏推荐

地址
【从零开始入门unity游戏开发之——C#篇】
【从零开始入门unity游戏开发之——unity篇】
【制作100个Unity游戏】
【推荐100个unity插件】
【实现100个unity特效】
【unity框架开发】

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

k8s基础(2)—Kubernetes-Namespace

一、Namespace概述 名字空间 在 Kubernetes 中,名字空间(Namespace) 提供一种机制,将同一集群中的资源划分为相互隔离的组。 同一名字空间内的资源名称要唯一,但跨名字空间时没有这个要求。 名字空间作用域仅针对带有…

iOS 逆向学习 - iOS Security Features:硬件与软件多重防护体系

iOS 逆向学习 - iOS Security Features:硬件与软件多重防护体系 iOS 安全特性全面解析:构筑多层次防御体系一、iOS 的硬件安全特性1. Secure Enclave(安全隔区)2. Hardware Root of Trust(硬件信任根)3. De…

计算机网络——数据链路层-流量控制和可靠传输

一、流量控制 流量控制是指由接收方及时控制发送方发送数据的速率,使接收方来得及接受。 • 停止等待流量控制 • 滑动窗口流量控制 1、停止—等待流量控制 停止-等待流量控制的基本原理是发送方每发出一帧后,就要等待接收方的应答信号&#xff…

Zookeeper是如何保证事务的顺序一致性的?

大家好,我是锋哥。今天分享关于【Zookeeper是如何保证事务的顺序一致性的?】面试题。希望对大家有帮助; Zookeeper是如何保证事务的顺序一致性的? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Zookeeper 通过多个机制来保证事务的顺序一…

实际开发中,常见pdf|word|excel等文件的预览和下载

实际开发中,常见pdf|word|excel等文件的预览和下载 背景相关类型数据之间的转换1、File转Blob2、File转ArrayBuffer3、Blob转ArrayBuffer4、Blob转File5、ArrayBuffer转Blob6、ArrayBuffer转File 根据Blob/File类型生成可预览的Base64地址基于Blob类型的各种文件的下载各种类型…

Qt使用CMake编译项目时报错:#undefined reference to `vtable for MainView‘

博主将.h文件和.cpp文件放到了不同的文件目录下面,如下图所示: 于是构建项目的时候就报错了#undefined reference to vtable for MainView,这个是由于src/view目录下的CMake无法自动moc头文件导致的,需要手动moc include/view目录…

会员制电商创新:开源 AI 智能名片与 2+1 链动模式的协同赋能

摘要:本文聚焦于电商领域会员制的关键作用,深入探讨在传统交易模式向数字化转型过程中,如何借助开源 AI 智能名片以及 21 链动模式商城小程序,实现对会员数据的精准挖掘与高效利用,进而提升企业的营销效能与客户洞察能…

第27周:文献阅读及机器学习

目录 摘要 Abstract 一、文献阅读 发现问题 研究方法 CNN-LSTM DT SVR 创新点 案例分析 数据准备 模型性能 预测模型的实现 仿真实验及分析 二、LSTM 1、基本结构 2、具体步骤 3、举例说明 4、原理理解 总结 摘要 本周阅读文献《Short-term water qua…

【机器遗忘之UNSIR算法】2023年IEEE Trans期刊论文:Fast yet effective machine unlearning

1 介绍 年份:2023 期刊:IEEE Transactions on Neural Networks and Learning Systems 引用量:170 Tarun A K, Chundawat V S, Mandal M, et al. Fast yet effective machine unlearning[J]. IEEE Transactions on Neural Networks and Le…

Linux-----进程处理(waitpid,进程树,孤儿进程)

目录 waitpid等待 进程树 孤儿进程 waitpid等待 Linux中父进程除了可以启动子进程,还要负责回收子进程的状态。如果子进程结束后父进程没有正常回收,那么子进程就会变成一个僵尸进程——即程序执行完成,但是进程没有完全结束,其…

Docker- Unable to find image “hello-world“locally

Docker- Unable to find image “hello-world“locally 文章目录 Docker- Unable to find image “hello-world“locally问题描述一. 切换镜像1. 编辑镜像源2. 切换镜像内容 二、 检查设置1、 重启dockers2、 检查配置是否生效3. Docker镜像源检查4. Dokcer执行测试 三、自定义…

Android配件应用默认启动与USB权限申请区别

使用效果: USB配件授权演示 选择USB配件默认打开应用 申请USB配件使用权限

vue2框架配置路由设计打印单

业务效果: 查询出列表后&#xff0c;点击申请单按钮&#xff0c;弹出申请表格&#xff0c;可进行打印 后端实现 控制器、服务层等省略&#xff0c;关联查出数据提供接口给前端即可 <!--获取详细信息(用于申请单打印)--><select id"selectXxxxDetail" par…

第29天:Web开发-PHP应用弱类型脆弱Hash加密Bool类型Array数组函数转换比较

#知识点 1、安全开发-原生PHP-弱类型脆弱 2、安全开发-原生PHP-函数&数据类型 3、安全开发-原生PHP-代码审计案例 一、PHP弱类型对比 1、 和 两个等号是弱比较&#xff0c;使用进行对比的时候&#xff0c;php解析器就会做隐式类型转换&#xff0c;如果两个值的类型不相等就…

在Ubuntu 18.04.6 LTS安装OpenFace流程

一、修改配置:将gcc8&#xff0c;g8作为默认选项 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 100 sudo update-alternatives --config gcc 选择版本&#xff0c;再查看gcc --version sudo update-alternatives --install /usr/bin/g g /usr/bin/g-…

【亚马逊云科技】基于Amazon EKS部署高可用的OceanBase的最佳实践

一、前言 随着企业业务的快速发展和数据量的不断增长&#xff0c;高性能、高可用的数据库解决方案成为了关键需求。OceanBase作为一款分布式关系型数据库&#xff0c;以其高扩展性、高可用性和高性能的特点&#xff0c;逐渐受到企业的广泛关注。然而&#xff0c;在复杂的分布式…

计算机网络:网络层知识点及习题(一)

网课资源&#xff1a; 湖科大教书匠 1、概述 网络层实现主机到主机的传输&#xff0c;主要有分组转发和路由选择两大功能 路由选择处理机得出路由表&#xff0c;路由表再生成转发表&#xff0c;从而实现分组从不同的端口转发 网络层向上层提供的两种服务&#xff1a;面向连接…

简历_熟悉缓存高并发场景处理方法,如缓存穿透、缓存击穿、缓存雪崩

系列博客目录 文章目录 系列博客目录1.缓存穿透总结 2.缓存雪崩3.缓存击穿代码总结 1.缓存穿透 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓存永远不会生效&#xff0c;这些请求都会打到数据库。 常见的解决方案有两种&#xff1a; 缓存空对…

阿里云 人工智能与机器学习

阿里云的 人工智能&#xff08;AI&#xff09;与机器学习&#xff08;ML&#xff09; 服务为企业提供了全面的AI解决方案&#xff0c;帮助用户在多个行业实现数据智能化&#xff0c;提升决策效率&#xff0c;推动业务创新。阿里云通过先进的技术和丰富的工具&#xff0c;支持用…