当前位置: 首页 > news >正文

特性(Attribute)

特性(Attribute)的概念

定义

特性是用于向代码元素(类、方法、属性等)添加元数据的类,继承自 System.Attribute。

元数据提供程序化的描述信息,供运行时或工具(如编译器、反射)使用。

作用:标记代码元素的附加信息(如序列化、权限控制、路由配置等)。通过反射机制,可在运行时动态读取并处理这些元数据。

内置案例

Obsolete 

Obsolete 特性是 C# 中的一个内置特性,用于标记某个类型、方法、属性等程序元素已经过时,不建议再使用。它能给开发者提供明确的提示,告知其应该使用新的替代方案。下面为你介绍几种 Obsolete 特性的应用案例。

1. 方法更新替换

在软件开发过程中,随着需求的变化和技术的发展,原有的方法可能会被更高效、更安全或者功能更强大的新方法所替代。此时就可以使用 Obsolete 特性标记旧方法,提醒开发者使用新方法。

using System;class Calculator
{// 标记为过时的方法[Obsolete("AddNumbers 方法已过时,请使用 NewAddNumbers 方法。", false)]public int AddNumbers(int a, int b){return a + b;}// 新的替代方法public int NewAddNumbers(int a, int b){// 可以在这里添加更复杂的逻辑return a + b;}
}class Program
{static void Main(){Calculator calculator = new Calculator();// 使用旧方法会产生编译警告int result = calculator.AddNumbers(2, 3);Console.WriteLine(result);// 使用新方法result = calculator.NewAddNumbers(2, 3);Console.WriteLine(result);}
}

在这个案例中,AddNumbers 方法被标记为过时,提示开发者使用 NewAddNumbers 方法。当调用 AddNumbers 方法时,编译器会给出警告。

2. 类型变更

如果某个类或者结构体的设计发生了重大变更,需要开发者使用新的类型,那么可以使用 Obsolete 特性标记旧类型。

using System;// 标记为过时的类
[Obsolete("OldPerson 类已过时,请使用 NewPerson 类。", false)]
class OldPerson
{public string Name { get; set; }public int Age { get; set; }
}// 新的替代类
class NewPerson
{public string FullName { get; set; }public int YearsOld { get; set; }
}class Program
{static void Main(){// 使用旧类会产生编译警告OldPerson oldPerson = new OldPerson();oldPerson.Name = "John";oldPerson.Age = 30;Console.WriteLine($"Old Person: {oldPerson.Name}, {oldPerson.Age}");// 使用新类NewPerson newPerson = new NewPerson();newPerson.FullName = "John Doe";newPerson.YearsOld = 30;Console.WriteLine($"New Person: {newPerson.FullName}, {newPerson.YearsOld}");}
}

这里 OldPerson 类被标记为过时,开发者应该使用 NewPerson 类。

3. 库版本升级

在开发类库时,随着版本的升级,可能会对一些公共接口或方法进行调整。为了保证旧代码的兼容性,同时引导开发者使用新的接口或方法,可以使用 Obsolete 特性。

using System;namespace MyLibrary
{public class MyService{// 旧版本的方法[Obsolete("OldMethod 方法已过时,从版本 2.0 开始请使用 NewMethod 方法。", false)]public void OldMethod(){Console.WriteLine("执行旧方法");}// 新版本的方法public void NewMethod(){Console.WriteLine("执行新方法");}}
}class Program
{static void Main(){MyLibrary.MyService service = new MyLibrary.MyService();// 使用旧方法会产生编译警告service.OldMethod();// 使用新方法service.NewMethod();}
}

在这个库升级的案例中,OldMethod 方法被标记为过时,提示开发者从版本 2.0 开始使用 NewMethod 方法。

4. 强制使用新特性

有时候,为了推动开发者尽快采用新的特性或功能,可以将 Obsolete 特性的第二个参数设置为 true,这样在使用过时元素时会产生编译错误,而不仅仅是警告。

using System;class DataProcessor
{// 标记为过时且使用时会产生编译错误[Obsolete("OldProcess 方法已过时,请使用 NewProcess 方法。", true)]public void OldProcess(){Console.WriteLine("执行旧处理逻辑");}public void NewProcess(){Console.WriteLine("执行新处理逻辑");}
}class Program
{static void Main(){DataProcessor processor = new DataProcessor();// 这里会产生编译错误// processor.OldProcess(); processor.NewProcess();}
}

此案例中,若尝试调用 OldProcess 方法,编译器会报错,强制开发者使用 NewProcess 方法。

Serializable 

Serializable 特性在 C# 里用于表明某个类型能够被序列化。序列化指的是把对象的状态转化成可存储或可传输的格式,反序列化则是将这种格式恢复成对象。下面为你提供几个不同场景下使用 Serializable 特性的案例代码。

案例 1:基本对象序列化与反序列化

以下代码展示了如何对一个简单的 Person 对象进行序列化和反序列化操作。

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;// 标记类为可序列化
[Serializable]
public class Person
{public string Name { get; set; }public int Age { get; set; }public Person(string name, int age){Name = name;Age = age;}
}class Program
{static void Main(){// 创建一个 Person 对象Person person = new Person("John", 30);// 序列化对象到文件using (FileStream stream = new FileStream("person.dat", FileMode.Create)){BinaryFormatter formatter = new BinaryFormatter();formatter.Serialize(stream, person);}// 从文件反序列化对象Person deserializedPerson;using (FileStream stream = new FileStream("person.dat", FileMode.Open)){BinaryFormatter formatter = new BinaryFormatter();deserializedPerson = (Person)formatter.Deserialize(stream);}// 输出反序列化后的对象信息Console.WriteLine($"Name: {deserializedPerson.Name}, Age: {deserializedPerson.Age}");}
}

案例 2:包含集合的对象序列化

下面的例子展示了对包含集合的 Team 对象进行序列化和反序列化。

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;// 标记类为可序列化
[Serializable]
public class Team
{public string TeamName { get; set; }public List<Person> Members { get; set; }public Team(string teamName){TeamName = teamName;Members = new List<Person>();}
}// 标记类为可序列化
[Serializable]
public class Person
{public string Name { get; set; }public int Age { get; set; }public Person(string name, int age){Name = name;Age = age;}
}class Program
{static void Main(){// 创建一个 Team 对象并添加成员Team team = new Team("Dream Team");team.Members.Add(new Person("Alice", 25));team.Members.Add(new Person("Bob", 28));// 序列化对象到文件using (FileStream stream = new FileStream("team.dat", FileMode.Create)){BinaryFormatter formatter = new BinaryFormatter();formatter.Serialize(stream, team);}// 从文件反序列化对象Team deserializedTeam;using (FileStream stream = new FileStream("team.dat", FileMode.Open)){BinaryFormatter formatter = new BinaryFormatter();deserializedTeam = (Team)formatter.Deserialize(stream);}// 输出反序列化后的团队信息Console.WriteLine($"Team Name: {deserializedTeam.TeamName}");foreach (var member in deserializedTeam.Members){Console.WriteLine($"Member: {member.Name}, Age: {member.Age}");}}
}

案例 3:使用 NonSerialized 特性排除字段

NonSerialized 特性可用于标记某个字段在序列化时应被忽略。

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;// 标记类为可序列化
[Serializable]
public class Employee
{public string Name { get; set; }public int Age { get; set; }[NonSerialized]private string password;public Employee(string name, int age, string password){Name = name;Age = age;this.password = password;}public string GetPassword(){return password;}
}class Program
{static void Main(){// 创建一个 Employee 对象Employee employee = new Employee("Eve", 32, "secretpassword");// 序列化对象到文件using (FileStream stream = new FileStream("employee.dat", FileMode.Create)){BinaryFormatter formatter = new BinaryFormatter();formatter.Serialize(stream, employee);}// 从文件反序列化对象Employee deserializedEmployee;using (FileStream stream = new FileStream("employee.dat", FileMode.Open)){BinaryFormatter formatter = new BinaryFormatter();deserializedEmployee = (Employee)formatter.Deserialize(stream);}// 输出反序列化后的对象信息Console.WriteLine($"Name: {deserializedEmployee.Name}, Age: {deserializedEmployee.Age}");// 密码字段在反序列化后为 nullConsole.WriteLine($"Password: {deserializedEmployee.GetPassword()}");}
}

DllImport

DllImport 是 C# 中的一个特性,用于从非托管动态链接库(DLL)中调用函数。在 .NET 开发中,有时需要使用一些由 C、C++ 等非托管语言编写的库来完成特定的任务,DllImport 特性就提供了一种在托管代码(如 C#)中调用这些非托管代码的方式。下面为你提供几个不同场景下使用 DllImport 的代码案例及解析。

案例 1:调用 Windows API 函数 MessageBox

Windows API 中有许多有用的函数,MessageBox 函数可以用于显示一个消息框。以下是调用该函数的代码示例:

using System;
using System.Runtime.InteropServices;class Program
{// 使用 DllImport 特性导入 user32.dll 中的 MessageBox 函数[DllImport("user32.dll", CharSet = CharSet.Auto)]public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);static void Main(){// 调用导入的 MessageBox 函数MessageBox(IntPtr.Zero, "这是一个消息框示例", "消息框标题", 0);}
}

解析

  • DllImport 特性[DllImport("user32.dll", CharSet = CharSet.Auto)] 表示要从 user32.dll 这个动态链接库中导入函数。CharSet = CharSet.Auto 用于指定字符集,这里设置为自动选择合适的字符集。

  • extern 关键字:在 C# 中,extern 关键字用于声明一个外部函数,该函数的实现是在外部的非托管代码中。

  • Main 方法:在 Main 方法中调用了导入的 MessageBox 函数,传入相应的参数来显示一个消息框。

案例 2:调用自定义的 C++ DLL 中的函数

假设我们有一个自定义的 C++ DLL,其中包含一个简单的加法函数。以下是具体的实现步骤和代码示例。

// MyMathLibrary.h
#ifdef MYMATHLIBRARY_EXPORTS
#define MYMATHLIBRARY_API __declspec(dllexport)
#else
#define MYMATHLIBRARY_API __declspec(dllimport)
#endifextern "C" MYMATHLIBRARY_API int Add(int a, int b);// MyMathLibrary.cpp
#include "MyMathLibrary.h"extern "C" MYMATHLIBRARY_API int Add(int a, int b)
{return a + b;
}--------------------------------------------
using System;
using System.Runtime.InteropServices;class Program
{// 使用 DllImport 特性导入自定义 DLL 中的 Add 函数[DllImport("MyMathLibrary.dll", CallingConvention = CallingConvention.Cdecl)]public static extern int Add(int a, int b);static void Main(){int result = Add(2, 3);Console.WriteLine($"2 + 3 = {result}");}
}

案例 3:调用 DLL 中处理字符串的函数

假设存在一个 C++ DLL 中的函数,用于反转字符串。

C++ DLL 代码(StringLibrary.dll

cpp

// StringLibrary.h
#ifdef STRINGLIBRARY_EXPORTS
#define STRINGLIBRARY_API __declspec(dllexport)
#else
#define STRINGLIBRARY_API __declspec(dllimport)
#endifextern "C" STRINGLIBRARY_API void ReverseString(char* str);// StringLibrary.cpp
#include "StringLibrary.h"
#include <cstring>
#include <algorithm>extern "C" STRINGLIBRARY_API void ReverseString(char* str)
{int len = strlen(str);std::reverse(str, str + len);
}

C# 调用代码

using System;
using System.Runtime.InteropServices;class Program
{// 导入 DLL 中的 ReverseString 函数[DllImport("StringLibrary.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]public static extern void ReverseString([In, Out] char[] str);static void Main(){string input = "hello";char[] charArray = input.ToCharArray();ReverseString(charArray);string result = new string(charArray);Console.WriteLine($"反转后的字符串: {result}");}
}

解析

  • [In, Out] 特性:表明该参数既可以作为输入,也可以作为输出。

  • CharSet = CharSet.Ansi:指定使用 ANSI 字符集。

  • Main 方法:将字符串转换为字符数组,调用 ReverseString 函数进行反转,再将结果转换回字符串并输出。

Conditional

Conditional 是 C# 中的一个特性,用于根据预处理器符号来控制方法是否被调用。在不同的编译条件下,使用该特性可以灵活地包含或排除某些代码逻辑,常用于调试、日志记录等场景。下面为你提供几个不同的 Conditional 代码案例及解析。

案例 1:调试信息输出

在开发过程中,我们常常需要输出一些调试信息,但在发布版本中又不希望这些信息影响性能或暴露敏感数据。这时可以使用 Conditional 特性来控制调试信息的输出。

#define DEBUG
using System;
using System.Diagnostics;class DebugHelper
{// 使用 Conditional 特性,只有定义了 DEBUG 符号时该方法才会被调用[Conditional("DEBUG")]public static void DebugMessage(string message){Console.WriteLine($"Debug: {message}");}
}class Program
{static void Main(){// 调用调试信息输出方法DebugHelper.DebugMessage("这是一条调试信息");Console.WriteLine("程序继续执行...");}
}

解析

  • #define DEBUG:这是一个预处理器指令,用于定义 DEBUG 符号。在 Visual Studio 中,默认的调试配置会自动定义这个符号。

  • [Conditional("DEBUG")]Conditional 特性指定了一个预处理器符号 "DEBUG"。只有当定义了这个符号时,DebugMessage 方法才会被调用;如果没有定义该符号,编译器会忽略对这个方法的调用。

  • Main 方法:在 Main 方法中调用了 DebugMessage 方法。由于我们定义了 DEBUG 符号,所以该方法会正常执行并输出调试信息。

案例 2:多条件控制

Conditional 特性也可以用于多个条件的控制。例如,我们可以根据不同的配置来决定是否执行某些特定的逻辑。

#define DEVELOPMENT
using System;
using System.Diagnostics;class FeatureManager
{// 只有定义了 DEVELOPMENT 符号时该方法才会被调用[Conditional("DEVELOPMENT")]public static void EnableDevelopmentFeature(){Console.WriteLine("开发环境特性已启用");}// 只有定义了 PRODUCTION 符号时该方法才会被调用[Conditional("PRODUCTION")]public static void EnableProductionFeature(){Console.WriteLine("生产环境特性已启用");}
}class Program
{static void Main(){// 调用开发环境特性方法FeatureManager.EnableDevelopmentFeature();// 由于没有定义 PRODUCTION 符号,该方法调用会被忽略FeatureManager.EnableProductionFeature();Console.WriteLine("程序继续执行...");}
}

解析

  • #define DEVELOPMENT:定义了 DEVELOPMENT 预处理器符号。

  • [Conditional("DEVELOPMENT")] 和 [Conditional("PRODUCTION")]:分别为两个方法指定了不同的预处理器符号。根据符号的定义情况,编译器会决定是否调用相应的方法。

  • Main 方法:调用了 EnableDevelopmentFeature 和 EnableProductionFeature 方法。由于只定义了 DEVELOPMENT 符号,所以只有 EnableDevelopmentFeature 方法会被执行。

案例 3:日志记录控制

在实际应用中,我们可能需要根据不同的日志级别来控制日志的输出。可以使用 Conditional 特性结合不同的预处理器符号来实现这一功能。

#define LOG_INFO
using System;
using System.Diagnostics;class Logger
{// 只有定义了 LOG_DEBUG 符号时该方法才会被调用[Conditional("LOG_DEBUG")]public static void DebugLog(string message){Console.WriteLine($"Debug: {message}");}// 只有定义了 LOG_INFO 符号时该方法才会被调用[Conditional("LOG_INFO")]public static void InfoLog(string message){Console.WriteLine($"Info: {message}");}// 只有定义了 LOG_ERROR 符号时该方法才会被调用[Conditional("LOG_ERROR")]public static void ErrorLog(string message){Console.WriteLine($"Error: {message}");}
}class Program
{static void Main(){// 由于没有定义 LOG_DEBUG 符号,该方法调用会被忽略Logger.DebugLog("这是一条调试日志");// 由于定义了 LOG_INFO 符号,该方法会被调用Logger.InfoLog("这是一条信息日志");// 由于没有定义 LOG_ERROR 符号,该方法调用会被忽略Logger.ErrorLog("这是一条错误日志");Console.WriteLine("程序继续执行...");}
}

解析

  • #define LOG_INFO:定义了 LOG_INFO 预处理器符号。

  • [Conditional("LOG_DEBUG")][Conditional("LOG_INFO")] 和 [Conditional("LOG_ERROR")]:分别为三个日志方法指定了不同的预处理器符号。根据符号的定义情况,编译器会决定是否调用相应的日志方法。

  • Main 方法:调用了三个日志方法。由于只定义了 LOG_INFO 符号,所以只有 InfoLog 方法会被执行。

Route

在 C# 中,Route 常用于 ASP.NET Core 应用程序里,它的作用是把传入的 HTTP 请求映射到对应的处理程序(像控制器中的操作方法)。下面会给出不同场景下的 Route 代码案例以及详细解析。

案例 1:基本路由配置

这个例子展示了如何在控制器里使用 [Route] 特性来定义基本的路由。

using Microsoft.AspNetCore.Mvc;// 定义控制器的路由前缀
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{// 定义 Get 方法的路由[HttpGet]public IActionResult Get(){return Ok("获取所有产品");}// 定义 Get 方法,通过 id 获取单个产品[HttpGet("{id}")]public IActionResult Get(int id){return Ok($"获取产品 ID 为 {id} 的产品");}// 定义 Post 方法,用于创建新的产品[HttpPost]public IActionResult Post(){return Ok("创建新的产品");}
}    

解析

  • [Route("api/[controller]")]:此特性设定了控制器的路由前缀。[controller] 属于占位符,会被控制器的名称(去掉 Controller 后缀)所替代,所以这个控制器的路由前缀是 api/Products

  • [HttpGet]:表示该方法处理 HTTP GET 请求。第一个 [HttpGet] 方法对应的路由是 api/Products,当客户端发送 GET 请求到这个地址时,就会调用这个方法。

  • [HttpGet("{id}")]:这个方法同样处理 HTTP GET 请求,不过它接收一个名为 id 的路由参数。对应的路由是 api/Products/{id},例如 api/Products/1,客户端发送这样的请求时,会调用这个方法。

  • [HttpPost]:表示该方法处理 HTTP POST 请求,对应的路由是 api/Products,当客户端发送 POST 请求到这个地址时,会调用这个方法。

案例 2:自定义路由模板

这个例子展示了如何运用自定义的路由模板。

using Microsoft.AspNetCore.Mvc;[ApiController]
public class OrdersController : ControllerBase
{// 自定义路由模板[Route("orders/{year:int}/{month:int}")][HttpGet]public IActionResult GetOrdersByDate(int year, int month){return Ok($"获取 {year} 年 {month} 月的订单");}
}    

解析

[Route("orders/{year:int}/{month:int}")]:这是一个自定义的路由模板,它规定了路由必须包含两个整数类型的参数 year 和 month。对应的路由是 orders/{year}/{month},例如 orders/2025/04,客户端发送这样的请求时,会调用 GetOrdersByDate 方法。

案例 3:路由约束

此例展示了如何使用路由约束来限制路由参数的类型和范围。

using Microsoft.AspNetCore.Mvc;[ApiController]
public class UsersController : ControllerBase
{// 使用路由约束[Route("users/{id:min(1)}")][HttpGet]public IActionResult GetUser(int id){return Ok($"获取用户 ID 为 {id} 的用户");}
}    

解析

  • [Route("users/{id:min(1)}")]:这里使用了路由约束 min(1),它规定了 id 参数的值必须大于或等于 1。如果客户端发送的请求中的 id 值小于 1,就不会匹配到这个路由。对应的路由是 users/{id},例如 users/2,客户端发送这样的请求时,会调用 GetUser 方法。

自定义特性

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;namespace WpfAppAttribute.UserAttribute
{// 定义自定义特性类// AttributeUsage 特性指定该自定义特性可以应用于哪些程序元素// 这里表示可以应用于类和方法[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]public class CustomDescriptionAttribute : Attribute{// 存储特性描述信息的属性public string Description { get; }// 构造函数,用于初始化描述信息public CustomDescriptionAttribute(string description){Description = description;}// 用于在方法执行前进行参数检查的方法public bool CheckParameters(object[] parameters){// 这里可以添加具体的参数检查逻辑// 示例:假设我们要求参数数组不为空且第一个参数为非空字符串if (parameters != null && parameters.Length > 0 && parameters[0] is string str && !string.IsNullOrEmpty(str)){return true;}return false;}}// 应用自定义特性到类[CustomDescription("这是一个示例类,用于演示自定义特性的使用。")]public class ExampleClass{// 应用自定义特性到方法[CustomDescription("这是一个示例方法,用于演示自定义特性的使用。")]public void ExampleMethod(string input){Console.WriteLine($"示例方法被调用,输入参数: {input}");}}public class AppAttribute{public void asdasd(){ExampleClass exampleClass = new ExampleClass();MethodInfo method = typeof(ExampleClass).GetMethod("ExampleMethod");CustomDescriptionAttribute attribute = method.GetCustomAttribute<CustomDescriptionAttribute>();string testInput = "test";object[] parameters = { testInput };if (attribute != null && attribute.CheckParameters(parameters)){method.Invoke(exampleClass, parameters);}else{Console.WriteLine("参数检查不通过,方法未执行。");}}}
}

解析:

直接在 asdasd 方法里面定义一个 CustomDescriptionAttribute 实例和通过反射从类或方法上获取特性实例,这两种方式存在明显区别,下面从几个方面进行详细分析:

1. 元数据的附着性

  • 使用特性标记类和方法
    在原代码中,CustomDescriptionAttribute 是通过特性语法(如 [CustomDescription("...")])直接标记在 ExampleClass 和 ExampleMethod 上的。这种方式使得描述信息成为了类和方法的元数据的一部分,它与类和方法紧密绑定,只要类和方法存在,这些元数据就可以在运行时通过反射机制随时获取。这种附着性让代码的描述信息具有更强的可读性和可维护性,开发者可以直观地看到类和方法的描述信息。

  • 在方法内部定义特性实例
    如果直接在 asdasd 方法内部创建 CustomDescriptionAttribute 实例,那么这个实例只是方法内部的一个局部对象,它与 ExampleClass 和 ExampleMethod 本身没有直接关联。它仅仅是在方法执行时临时存在的一个对象,无法体现类和方法的固有属性。

2. 代码的可扩展性和复用性

  • 使用特性标记类和方法
    当使用特性标记类和方法时,其他部分的代码也可以通过反射来获取这些特性信息,从而实现更多的功能。例如,可以编写一个通用的工具类,用于遍历程序集中的所有类和方法,获取它们的 CustomDescriptionAttribute 描述信息,生成文档或者进行其他处理。这种方式使得特性可以在多个地方复用,提高了代码的可扩展性。

  • 在方法内部定义特性实例
    在方法内部定义的特性实例只能在该方法内部使用,无法被其他方法或类访问。这就限制了特性的复用性,当需要在其他地方使用相同的检查逻辑时,就需要重复编写代码。

3. 代码的可读性和维护性

  • 使用特性标记类和方法
    通过特性标记类和方法,代码的意图更加清晰。开发者在查看类和方法的定义时,一眼就能看到它们的描述信息和相关的检查逻辑。同时,当需要修改描述信息或检查逻辑时,只需要修改特性的定义和标记即可,不会影响到其他部分的代码。

  • 在方法内部定义特性实例
    在方法内部定义特性实例会使方法的代码变得复杂,因为它不仅包含了方法的核心逻辑,还包含了特性的创建和使用逻辑。这会降低代码的可读性,并且当需要修改特性逻辑时,可能会影响到方法的其他部分。

特性的目标与参数

可通过 AttributeTargets 枚举指定适用目标(类、方法、属性、参数等)。

[AttributeUsage(AttributeTargets.Property)]
public class RangeCheckAttribute : Attribute { }

参数类型

  • 位置参数:通过构造函数传递,必须按顺序赋值。

  • 命名参数:通过属性赋值,可任意顺序。

[Author("张三", Version = 2.0)] // Version 是命名参数
public class Book { }

反射与特性

  1. 读取特性

    • 通过反射 API(如 GetCustomAttributes())获取特性信息。

var attributes = typeof(ExampleClass).GetCustomAttributes(typeof(MyCustomAttribute), false);
  1. 典型应用场景

    1.动态生成文档(如标记方法的用途)。
  2. 自动化测试框架(标记测试类和测试方法)。
  3. Web 框架路由配置(如 ASP.NET Core)。

注意事项

  1. 命名规范:特性类名通常以 Attribute 结尾,但使用时可以省略(如 [MyCustom] 等价于 [MyCustomAttribute])。

  2. 作用范围控制:使用 AttributeUsage 的 Inherited 属性控制是否允许继承。

  3. 性能考量:反射操作可能影响性能,避免频繁调用。

总结

特性(Attribute)是 .NET 中强大的元数据机制,广泛应用于框架设计、代码分析、配置管理等场景。掌握其基本用法和反射结合方式,能显著提升代码的灵活性和可维护性。

相关文章:

特性(Attribute)

特性&#xff08;Attribute&#xff09;的概念 定义 特性是用于向代码元素&#xff08;类、方法、属性等&#xff09;添加元数据的类&#xff0c;继承自 System.Attribute。 元数据提供程序化的描述信息&#xff0c;供运行时或工具&#xff08;如编译器、反射&#xff09;使…...

使用CubeMX新建SysTick延时函数工程——使用中断,不使用HAL_Delay

具体操作步骤看这里&#xff1a;STM32CubeMX学习笔记&#xff08;4&#xff09;——系统延时使用_cubemx systick-CSDN博客 1、SysTick 初始化函数 SysTick 初始化函数由用户编写&#xff0c;里面调用了 SysTick_Config() 这个固件库函数&#xff0c;通过设置该固件 库函数的形…...

从零开始实现 MobileViT 注意力机制——轻量级Transformer Vision Model 的新思路

从零开始实现 MobileViT 注意力机制——轻量级Transformer Vision Model 的新思路 近年来&#xff0c;计算机视觉领域中 Transformer 模型的崛起为图像处理带来了新的活力。特别是在 ViT&#xff08;Vision Transformer&#xff09;模型提出之后&#xff0c;Transformer 在图像…...

Doris部署生产集群最低要求的部署方案

Doris生产集群最低部署方案&#xff08;2025年4月版&#xff09; 一、节点规划与数量 1. FE节点&#xff08;Frontend&#xff09; 数量&#xff1a;至少 3个节点&#xff08;1个Follower 2个 Observer&#xff09;&#xff0c;确保高可用&#xff08;HA&#xff09;。角色分…...

如何实现“一机两用” 寻求安全与效率的完美平衡

#### 一机两用的背景 在数字化时代&#xff0c;无论是企业还是政府部门&#xff0c;都面临着既要处理内部敏感数据&#xff0c;又要访问互联网获取资源的双重需求。这种需求催生了“一机两用”的模式&#xff0c;即同一台终端设备既要连接内网处理核心业务&#xff0c;又要能够…...

楼宇自控系统如何为现代建筑打造安全、舒适、节能方案

在科技飞速发展的当下&#xff0c;现代建筑对功能和品质的要求日益提升。楼宇自控系统作为建筑智能化的核心技术&#xff0c;宛如一位智慧的“管家”&#xff0c;凭借先进的技术手段&#xff0c;为现代建筑精心打造安全、舒适、节能的全方位解决方案&#xff0c;让建筑真正成为…...

Xilinx 7系列fpga在线升级和跳转

一、常见跳转方式 1&#xff0c;一般FPGA只要上电&#xff0c;就会自动从外部flash的0地址加载程序。 2&#xff0c;而我们所谓的在线式升级就是在flash0地址放一个程序&#xff08;boot/golden image&#xff09;&#xff0c;然后在后面再放一个程序&#xff08;app/update …...

【LangChain核心组件】Callbacks机制深度剖析与实战指南

目录 一、通俗解释&#xff08;举个&#x1f330;&#xff09; 二、具体能干啥&#xff1f; 三、怎么用&#xff1f;&#xff08;一句话说透&#xff09; 四、小结 五、为什么Callbacks是LangChain的灵魂组件&#xff1f; 六、Callbacks核心API解析 1、 基础回调处理器 …...

回调函数用法详细讲解

目录 一、通过几个例子&#xff0c;浅谈一下我的学习见解&#xff01; 二、typedef关键字用法回顾 1&#xff09;基本语法 2&#xff09;主要用途 1、为基本数据类型定义别名 2、为复杂类型定义别名 >>1.数组类型 >>2.指针类型 >>3.结构体类型 >…...

Nature子刊:科学家绘制与全身性癫痫发作相关的大脑网络图谱,为新的脑刺激疗法铺平道路

癫痫是一种古老的神经系统疾病&#xff0c;其历史可以追溯到数千年前。在古代&#xff0c;癫痫患者常被误解为受到神灵的惩罚或灵魂的附体&#xff0c;这种误解导致患者在社会中遭受歧视和排斥。然而&#xff0c;随着现代医学的发展&#xff0c;我们逐渐揭开了癫痫的神秘面纱&a…...

postman使用技巧

postman使用技巧 pre-request需求&#xff1a;三方对接的接口需要在请求头中添加如下参数pre-request 中获取环境变量中的变量值pre-request 中添加请求头 参考&#xff1a; pre-request 需求&#xff1a;三方对接的接口需要在请求头中添加如下参数 Accept: application/json…...

代码随想录算法训练营第十九天

LeetCode题目: 77. 组合216. 组合总和 III17. 电话号码的字母组合2537. 统计好子数组的数目(每日一题)516. 最长回文子序列1039. 多边形三角剖分的最低得分543. 二叉树的直径124. 二叉树中的最大路径和2246. 相邻字符不同的最长路径 其他: 今日总结 往期打卡 77. 组合 跳转: 7…...

MySQL联表查询底层原理

MySQL联表查询底层原理 1. 连接算法概述 MySQL在执行联表查询时&#xff0c;主要使用以下三种算法&#xff1a; 1.1 嵌套循环连接&#xff08;Nested-Loop Join&#xff09; -- 基本原理&#xff1a;对于左表的每一行&#xff0c;都要在右表中查找所有匹配的行 -- 示例查询…...

静态链接part2

编译 语义分析 由语义分析器完成&#xff0c;这个步骤只是完成了对表达式的语法层面的分析&#xff0c;它并不了解这个语句是否真的有意义&#xff08;例如在C语言中两个指针做乘法运算&#xff0c;这个语句在语法上是合法的&#xff0c;但是没有什么意义&#xff1b;还有同样…...

在边缘端进行tensorflow模型的部署(小白初探)

1.配置tensorflow的环境 &#xff08;我是安装GPU版本的&#xff09; 建议参考这个博主的文章&#xff0c;确实非常快速&#xff01; 十分钟安装Tensorflow-gpu2.6.0本机CUDA12 以及numpymatplotlib各包版本协调问题_tensorflow cuda12-CSDN博客 2.学习自制数据集 &#xf…...

合成数据如何赋能大模型预训练:效果与效率的双重加速器

目录 合成数据如何赋能大模型预训练&#xff1a;效果与效率的双重加速器 一、预训练模型为何需要合成数据&#xff1f; ✅ 克服真实数据的稀缺与偏倚 ✅ 控制训练内容结构与分布 ✅ 提升学习效率与训练稳定性 二、哪些预训练任务适合用合成数据&#xff1f; 三、如何构建…...

【n8n docker 部署的代理问题】解决n8n部署无法访问openai等外国大模型厂商的api

n8n docker 部署的代理问题&#xff1a;解决无法访问 OpenAI 等外国大模型厂商的 API 问题背景 在使用 n8n 进行自动化工作流开发时&#xff0c;经常需要调用 OpenAI 等外国大模型厂商的 API。然而&#xff0c;由于网络限制&#xff0c;直接部署的 n8n 容器无法访问这些 API …...

MongoDB 分账号限制数据访问

MongoDB 分账号限制数据访问 在 MongoDB 中&#xff0c;可以通过几种方式实现不同账号只能访问特定数据的需求&#xff0c;类似于你在 PostgreSQL 中实现的功能。 1. 基于角色的访问控制 (RBAC) 创建用户并分配角色 // 创建只能读取特定数据库的用户 use admin db.createUs…...

可控硅的工作原理和设计参考

可控硅物理结构如下图所示&#xff0c;P-N-P-N&#xff0c;就象两只背靠背的三极管。我们先来分析栅极不作电气联接的情况。当可控硅阴极电位大于阳极电位&#xff0c;J1和J3结反偏&#xff0c;器件截止。当可控硅阴极电位小于阳极电位&#xff0c;J1和J3正偏&#xff0c;但J2反…...

搭建axure cloud私有化平台

要求 https://blog.csdn.net/ss810540895/article/details/145833470 能不能找个空闲服务器&#xff0c;搭建一下 axure服务器&#xff0c;之前他们提供的免费服务终止了&#xff0c;我们需要尽快搭建一下服务。 步骤 mysql 数据库密码 Tbit36987. 分配权限 CREATE USER root…...

【无标题】spark SQL核心编程

MySQL Spark SQL 可以通过 JDBC 从关系型数据库中读取数据的方式创建 DataFrame&#xff0c;通过对 DataFrame 一系列的计算后&#xff0c;还可以将数据再写回关系型数据库中。 IDEA通过JDBC对MySQL进行操作&#xff1a; 1&#xff09; 导入依赖 <dependency> &l…...

PostgreSql dump导入问题集合

PostgreSql dump导入问题集合 删除数据库无法删除问题 SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE datnametest AND pid<>pg_backend_pid();版本检查 pg_restore -l D:/suian/vrms2_backup.dump > D:/suian/vrms22list.txt…...

使用DeepSeek如何提升课题申报书中研究内容的专业性?25个进阶DeepSeek指令

家人们&#xff01;搞课题申报的是不是都知道&#xff0c;课题申报书里的研究内容那可是重中之重&#xff0c;写得专业不专业直接影响申报能不能成功。今天咱就来唠唠怎么用DeepSeek提升课题申报书中研究内容的专业性&#xff0c;我还给大家准备了25个进阶使用小妙招哦&#xf…...

文章记单词 | 第33篇(六级)

一&#xff0c;单词释义 poison [ˈpɔɪzn] n. 毒药&#xff1b;毒物&#xff1b;有害的思想&#xff08;或心情等&#xff09;&#xff1b;vt. 毒死&#xff1b;毒害&#xff1b;下毒&#xff1b;在… 中放毒&#xff1b;污染&#xff1b;adj. 有毒的justification [ˌdʒʌ…...

【LangChain核心组件】Memory:让大语言模型拥有持续对话记忆的工程实践

目录 一、Memory架构设计解析 1. 核心组件关系图 2. 代码中的关键实现 二、对话记忆的工程实现 1. 消息结构化存储 2. 动态提示组装机制 三、Memory类型选型指南 四、生产环境优化实践 1. 记忆容量控制 2. 记忆分片策略 3. 记忆检索增强 五、典型问题调试技巧 1. …...

GD32裸机程序-SFUD接口文件记录

SFUD gitee地址 SFUD spi初始化 /********************************************************************************* file : bsp_spi.c* author : shchl* brief : None* version : 1.0* attention : None* date : 25-…...

天元证券|调仓曝光!首批科技基金一季报出炉

4月15日&#xff0c;中欧基金、永赢基金、长城基金等公募基金公司旗下部分权益类基金产品一季报出炉。 券商中国记者梳理发现&#xff0c;永赢信息产业智选混合主要聚焦信息技术领域布局&#xff0c;前十大重仓股中9只股票属于信息技术行业&#xff0c;合计占基金资产净值比例达…...

【开源项目】Excel手撕AI算法深入理解(四):AlphaFold、Autoencoder

项目源码地址&#xff1a;https://github.com/ImagineAILab/ai-by-hand-excel.git 一、AlphaFold AlphaFold 是 DeepMind 开发的突破性 AI 算法&#xff0c;用于预测蛋白质的三维结构。它的出现解决了生物学领域长达 50 年的“蛋白质折叠问题”&#xff0c;被《科学》杂志评为…...

React-router v7 第四章(路由传参)

参数传递 React-router 一共有三种方式进行参数传递&#xff0c;参数传递指的是在路由跳转时&#xff0c;将参数传递给目标路由。 Query方式 Query的方式就是使用 ? 来传递参数&#xff0c;例如&#xff1a; #多个参数用 & 连接 /user?name小满zs&age18跳转方式&…...

常用密码技术初探

记得前几年有一部电影叫做《解除好友2&#xff1a;暗网》&#xff0c;它讲述了主角捡到一台电脑&#xff0c;并用它与好友进行视频通讯&#xff0c;但一名黑客通过网络技术篡改了通讯内容&#xff0c;最终导致所有参与视频通话的人都遭遇不测。 电影当然存在夸张成分&#xff…...

电脑知识 | TCP通俗易懂详解 <二>tcp首部

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f583;TCP快递单填写&#xff08;必填部分&#xff09; 1.&#x1f338;TCP快递单样式 2.&#x1f3e2;填写名称 3.&#x1f522;TCP序号 4. ✔️TCP确认号 ​编辑5.✅️确认号的确认号 6.&#x1f4cf;首部长度 …...

09-RocketMQ 深度解析:从原理到实战,构建可靠消息驱动微服务

RocketMQ 深度解析&#xff1a;从原理到实战&#xff0c;构建可靠消息驱动微服务 一、RocketMQ 核心定位与架构探秘 1.1 分布式消息领域的中流砥柱 在分布式系统中&#xff0c;消息队列是实现异步通信、解耦服务、削峰填谷的关键组件。RocketMQ 作为阿里巴巴开源的分布式消息…...

MyBatis 如何使用

1. 环境准备 添加依赖&#xff08;Maven&#xff09; 在 pom.xml 中添加 MyBatis 和数据库驱动依赖&#xff1a; <dependencies><!-- MyBatis 核心库 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId&g…...

AI日报 - 2025年04月17日

&#x1f31f; 今日概览(60秒速览) ▎&#x1f916; AGI突破 | OpenAI新模型或证人类未解定理&#xff0c;研究达Level 4 OpenAI安全博客暗示模型将创造新科学&#xff0c;能连接概念提新实验。CEO预测AI将证明人类未解定理&#xff0c;研究员称已达AGI第四层级。 ▎&#x1f4…...

【Leetcode-Hot100】缺失的第一个正数

题目 解答 有一处需要注意&#xff0c;我使用注释部分进行交换值&#xff0c;报错&#xff1a;超出时间限制。有人知道是为什么吗&#xff1f;难道是先给nums[i]赋值后&#xff0c;从而改变了后一项的索引&#xff1f; class Solution(object):def firstMissingPositive(sel…...

Servlet简单示例

Servlet简单示例 文章说明 Servlet 虽然是一门旧技术了&#xff0c;但是它的基础性和广泛性仍然不可忽视&#xff1b;我在实践中发现不少同学经常会被它的一些特性给困惑住&#xff1b;时常出现404等错误&#xff0c;这里我写下这篇文章&#xff0c;介绍Servlet的不同版本的特…...

spring:注解@Component、@Controller、@Service、@Reponsitory

背景 spring框架的一个核心功能是IOC&#xff0c;就是将Bean初始化加载到容器中&#xff0c;Bean是如何加载到容器的&#xff0c;可以使用spring注解方式或者spring XML配置方式。 spring注解方式直接对项目中的类进行注解&#xff0c;减少了配置文件内容&#xff0c;更加便于…...

LLM做逻辑推理题 - 野鸭蛋的故事

题目: 四个旅游家&#xff08;张虹、印玉、东晴、西雨&#xff09;去不同的岛屿去旅行&#xff0c;每个人都在岛上发现了野鸡蛋&#xff08;1个到3个&#xff09;。4人的年龄各不相同&#xff0c;是由18岁到21岁。已知&#xff1a; ①东晴是18岁。 ②印玉去了A岛。 ③21岁的女…...

Linux的目录结构(介绍,具体目录结构)

目录 介绍 具体目录结构 简洁的目录解释 详细的目录解释 介绍 Linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构的最上层是根目录“/”。Linux的世界中&#xff0c;一切皆文件&#xff08;比如&#xff1a;Linux会把硬件映射成文件来管理&#xff09; 具体目…...

C++Cherno 学习笔记day21 [86]-[90] 持续集成、静态分析、参数计算顺序、移动语义、stdmove与移动赋值操作符

b站Cherno的课[86]-[90] 一、C持续集成二、C静态分析三、C的参数计算顺序四、C移动语义五、stdmove与移动赋值操作符 一、C持续集成 Jenkins 商业软件 二、C静态分析 静态分析器会检查你的代码&#xff0c;并尝试检测各种错误&#xff0c;这些错误 可能是你无意中编写的&am…...

python学习 -- 综合案例1:设计一款基于python的飞机大战小游戏

本文目录 pygame模块介绍核心模块与功能开发流程 本文案例 - 飞机大战开发流程1. 导入必要的库2. 定义常量3. 创建精灵类4. 主程序 运行游戏 总结 pygame模块介绍 Pygame 是基于 Python 的开源、跨平台游戏开发库&#xff0c;依托 SDL&#xff08;Simple DirectMedia Layer&am…...

开启 Python 编程之旅:基础入门实战班全解析

重要的东西放前面 开启 Python 编程之旅&#xff1a;基础入门实战班全解析 开启Python编程之旅&#xff1a;基础入门实战班全解析 在当下热门的编程语言中&#xff0c;Python凭借简洁易读的语法、强大的功能和丰富的库&#xff0c;在数据科学、人工智能、Web开发等诸多领域大…...

Linux笔记---动静态库(原理篇)

1. ELF文件格式 动静态库文件的构成是什么样的呢&#xff1f;或者说二者的内容是什么&#xff1f; 实际上&#xff0c;可执行文件&#xff0c;目标文件&#xff0c;静态库文件&#xff0c;动态库文件都是使用ELF文件格式进行组织的。 ELF&#xff08;Executable and Linkable…...

SpringBoot整合Logback日志框架深度实践

一、依赖与默认集成机制 SpringBoot从2.x版本开始默认集成Logback日志框架&#xff0c;无需手动添加额外依赖。当项目引入spring-boot-starter-web时&#xff0c;该组件已包含spring-boot-starter-logging&#xff0c;其底层实现基于LogbackSLF4J组合。这种设计使得开发者只需…...

Spring Boot中接入DeepSeek的流式输出

第一步&#xff0c;添加依赖&#xff1a; <!-- WebFlux 响应式支持 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId> </dependency> 第二步&#xff0c;配置We…...

路由交换网络专题 | 第四章 | 生成树 | VRRP | 边缘端口

拓扑图 &#xff08;1&#xff09;SW1、SW2、SW3 三台交换机之间存在环路问题&#xff0c;需要通过生成树协议破环&#xff0c;请简述二层环路可能导致的问题。 因为交换机在收到一个广播帧之后&#xff0c;会对非接收端口进行转发。每台交换机都转发的话&#xff0c;就行形成一…...

SFOS2:常用容器(布局)介绍

一、前言 最近在进行sailfish os的开发&#xff0c;由于在此之前并没有从事过QT开发的工作&#xff0c;所以对这一套颇为生疏&#xff0c;以此记录一下。以下内容不一定完全准确&#xff0c;开发所使用的是Qt Quick 2.6与Sailfish.Silica 1.0两个库。 二、布局 1.Qt Quick 2.…...

VS qt 联合开发环境下的多国语言翻译

添加Linguist 文件方法&#xff0c;如同添加类文件的方式&#xff0c;那样&#xff1a; 其他跟QT的一样的流程&#xff0c;另外在main函数里要注册一下&#xff0c; QTextCodec::setCodecForLocale(textCodec); QTranslator translator5; QString trans5 fi…...

基于 Python 的 ROS2 应用开发全解析

引言 在机器人操作系统&#xff08;ROS&#xff09;不断发展的进程中&#xff0c;ROS2 作为新一代的机器人框架&#xff0c;带来了诸多显著的改进与新特性。Python 作为一种简洁、高效且具有强大数据处理能力的编程语言&#xff0c;在 ROS2 应用开发中占据着重要地位。本文将深…...

AI分析师

01 实操 人工 公司需要开发了一个XX系统&#xff0c;在文件夹中包含了XX.csv&#xff0c;其中每一行表示一个XX样本&#xff0c;最后一列为每个样本的标签&#xff0c;现需要设计模型与系统&#xff0c;请按照以下要求完成算法测试。根据要求完成以下任务&#xff0c;将完成的…...