在 C# 中,特性(Attributes)是一种用于向代码添加元数据的强大机制。这些元数据可以被编译器、运行时环境或开发工具读取,并用于控制程序的行为。本文将详细介绍C#中特性的基本概念、常见用法以及一些高级应用。
System.Attribute
的类。特性使用方括号[]
表示,通常放置在它们所修饰的元素之前。例如:
[Serializable]
public class MyClass
{
// 类的定义
}
在这个例子中,[Serializable]
是一个特性,它表示 MyClass
可以被序列化。
[Obsolete]
等特性标记代码问题。C# 提供了许多内置特性,涵盖了从序列化到性能优化的各种用途。下面介绍一些常用的内置特性。
[Serializable]
序列化控制:用于标记一个类可以被序列化。这意味着该类的对象可以被转换为字节流,以便于存储或传输。
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
[Obsolete]
过时标记:用于标记某个成员已过时,建议用户停止使用。你可以提供一个消息来解释为什么这个成员不再推荐使用。
public class Calculator
{
[Obsolete("Use Add method instead.")]
public int Sum(int a, int b)
{
return a + b;
}
public int Add(int a, int b)
{
return a + b;
}
}
参数 error
:可指定消息和是否引发编译器错误
[Obsolete("请改用 NewMethod(),此方法将在 v2.0 移除", error: true)]
public void OldMethod() { } // 引发编译错误
编译时触发警告或错误提示,强制代码升级。
[Conditional]
条件编译:用于标记一个方法,使其只有在特定条件满足时才会被调用。最常用的是 DEBUG
和 TRACE
条件。
using System.Diagnostics;
public class Logger
{
[Conditional("DEBUG")]
public void Log(string message)
{
Console.WriteLine(message);
}
}
仅在定义了 DEBUG
符号时编译和执行该方法,适合调试日志。
[DllImport]
用于声明一个外部函数,通常是来自非托管代码(如C/C++库)。这使得你可以调用这些外部函数。
using System.Runtime.InteropServices;
class Program
{
[DllImport("user32.dll")]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
static void Main()
{
MessageBox(IntPtr.Zero, "Hello, World!", "Title", 0);
}
}
[Flags]
枚举位标志:[Flags]
特性用于表示枚举类型是位标志,支持位运算。
[Flags]
public enum Permissions
{
None = 0,
Read = 1,
Write = 2,
All = Read | Write
}
[STAThread]
[STAThread]
特性用于指定线程的Apartment State,适用于Windows Forms应用程序。
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
[MethodImpl]
[MethodImpl]
特性用于控制方法的实现方式,如内联建议。
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Add(int a, int b)
{
return a + b;
}
用于设置程序集的标题。
[assembly: AssemblyTitle("MyApplication")]
用于设置程序集的描述。
[assembly: AssemblyDescription("This is a sample application.")]
用于设置程序集的版本号。
[assembly: AssemblyVersion("1.0.0.0")]
用于设置程序集的文件版本号。
[assembly: AssemblyFileVersion("1.0.0.0")]
用于指定XML序列化时的根元素名称。
[XmlRoot("Person")]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
用于指定XML序列化时的元素名称。
public class Person
{
[XmlElement("FullName")]
public string Name { get; set; }
[XmlElement("Years")]
public int Age { get; set; }
}
用于指定XML序列化时的属性名称。
public class Person
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("age")]
public int Age { get; set; }
}
用于指定序列化时忽略某个属性,常用于JSON序列化。
public class Person
{
public string Name { get; set; }
[JsonIgnore]
public int Age { get; set; }
}
用于指定JSON序列化时的属性名称。
public class Person
{
[JsonProperty("full_name")]
public string Name { get; set; }
[JsonProperty("years")]
public int Age { get; set; }
}
除了使用内置特性外,你还可以创建自己的特性。自定义特性可以帮助你在项目中实现特定的需求。开发者自行定义,用于扩展元数据,如权限控制、日志标记等。
要创建自定义特性,你需要继承 Attribute
类并定义所需的构造函数和属性。
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CustomAttribute : Attribute
{
public string Description { get; }
public CustomAttribute(string description)
{
Description = description;
}
}
可以通过继承Attribute
类定义自定义特性,并使用[AttributeUsage]
指定其适用范围。
AttributeUsage
:指定特性可以应用于哪些目标(如类、方法等),并设置是否允许多次应用。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple = true,
Inherited = false)]
public class AuthorAttribute : Attribute
{
public string Name { get; }
public string Version { get; set; }
public AuthorAttribute(string name)
{
Name = name;
}
}
特性命名约定以 Attribute
结尾,但使用时可以省略Attribute
(如:[Author("张三")]
)。
通过使用AttributeUsageAttribute
,可以指定自定义特性可以应用的作用范围。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyCustomAttribute : Attribute
{
// 特性的定义
}
AttributeTargets.Class
:表示特性可以应用于类。AttributeTargets.Method
:表示特性可以应用于方法。AttributeTargets.Property
、AttributeTargets.Field
等。一旦定义了自定义特性,你就可以像使用内置特性一样使用它。
[Custom("This is a custom attribute for the class.")]
public class MyClass
{
[Custom("This is a custom attribute for the method.")]
public void MyMethod()
{
// 方法体
}
}
你可以使用反射来读取特性信息。以下是一个简单的示例:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CustomAttribute : Attribute
{
public string Description { get; }
public CustomAttribute(string description)
{
Description = description;
}
}
[Custom("This is a custom attribute for the class.")]
public class MyClass
{
[Custom("This is a custom attribute for the method.")]
public void MyMethod()
{
// 方法体
}
}
class Program
{
static void Main(string[] args)
{
Type type = typeof(MyClass);
// 检查类上的特性
var classAttributes = type.GetCustomAttributes(typeof(CustomAttribute), false);
foreach (var attr in classAttributes)
{
Console.WriteLine(((CustomAttribute)attr).Description);
// 输出:This is a custom attribute for the class.
}
// 检查方法上的特性
var method = type.GetMethod("MyMethod");
var methodAttributes = method.GetCustomAttributes(typeof(CustomAttribute), false);
foreach (var attr in methodAttributes)
{
Console.WriteLine(((CustomAttribute)attr).Description);
// 输出:This is a custom attribute for the method.
}
}
}
继承 System.Attribute
并通过 AttributeUsage
标记适用范围:
// 定义特性:只能用于属性,且不可继承
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public sealed class MyCustomAttribute : Attribute
{
public string Description { get; }
public int Priority { get; }
public MyCustomAttribute(string description, int priority = 1)
{
Description = description;
Priority = priority;
}
}
public class Product
{
[MyCustom("产品名称", 1)]
public string Name { get; set; }
[MyCustom("价格", 2)]
public decimal Price { get; set; }
}
class Program
{
static void Main(string[] args)
{
var product = new Product();
var properties = typeof(Product).GetProperties();
foreach (var prop in properties)
{
var attr = prop.GetCustomAttribute<MyCustomAttribute>();
if (attr != null)
{
Console.WriteLine($"属性 {prop.Name} 描述:{attr.Description}, 优先级:{attr.Priority}");
}
}
}
}
typeof(Product).GetProperties();
中使用 GetProperties
获取属性,还可以使用GetMethods 获取方法,或使用获取其他成员的Getxxx
方法。
运行结果:
属性 Name 描述:产品名称, 优先级:1
属性 Price 描述:价格, 优先级:2
特性不仅可以在日常编码中简化工作,还可以在大型项目中发挥重要作用。以下是几个应用场景。
在进行对象的序列化和反序列化时,可以使用特性来控制哪些字段或属性需要被序列化。
using System;
using System.Runtime.Serialization;
[Serializable]
public class Person
{
[IgnoreDataMember]
public string TemporaryData { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
}
[Serializable]
public class DataPacket
{
[NonSerialized]
private string _secretKey;
[JsonPropertyName("packet_id")]
public int Id { get; set; }
}
你可以使用特性来进行数据验证。例如,在ASP.NET Core中,数据注解特性可以用来验证模型数据。
using System.ComponentModel.DataAnnotations;
public class User
{
[Required(ErrorMessage = "用户名不能为空")]
[StringLength(20, MinimumLength = 5)]
public string Username { get; set; }
[EmailAddress]
public string Email { get; set; }
[Range(0, 100)]
public int Age { get; set; }
}
public class RangeAttribute : Attribute
{
public int Min { get; }
public int Max { get; }
public RangeAttribute(int min, int max)
{
Min = min;
Max = max;
}
}
public static void Validate(object obj)
{
var properties = obj.GetType().GetProperties();
foreach (var prop in properties)
{
var rangeAttr = prop.GetCustomAttribute<RangeAttribute>();
if (rangeAttr != null)
{
int value = (int)prop.GetValue(obj);
if (value < rangeAttr.Min || value > rangeAttr.Max)
throw new ValidationException($"{prop.Name} 值超出范围");
}
}
}
在构建 Web API 时,可以使用特性来定义路由、HTTP 方法等。
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
// 返回用户列表
}
[HttpPost]
public IActionResult Post([FromBody] User user)
{
// 创建新用户
}
}
通过特性结合AOP(面向切面编程)技术,可以实现自动化的日志记录功能。这样可以减少重复代码,提高代码的可维护性。
using System;
public class LoggingAttribute : Attribute
{
public void OnEntry(string methodName)
{
Console.WriteLine($"Entering method: {methodName}");
}
public void OnExit(string methodName)
{
Console.WriteLine($"Exiting method: {methodName}");
}
}
public class Service
{
[Logging]
public void DoSomething()
{
Console.WriteLine("Doing something...");
}
}
public static class MethodInvoker
{
public static void InvokeWithLogging(object target, string methodName)
{
var method = target.GetType().GetMethod(methodName);
var attributes = (LoggingAttribute[])method.GetCustomAttributes(typeof(LoggingAttribute), false);
foreach (var attr in attributes)
{
attr.OnEntry(methodName);
}
method.Invoke(target, null);
foreach (var attr in attributes)
{
attr.OnExit(methodName);
}
}
}
class Program
{
static void Main(string[] args)
{
var service = new Service();
MethodInvoker.InvokeWithLogging(service, nameof(Service.DoSomething));
}
}
运行结果:
Entering method: DoSomething
Doing something...
Exiting method: DoSomething
Rougamo.Fody
using Rougamo;
using Rougamo.Context;
class Program
{
static void Main(string[] args)
{
Service service = new Service();
service.DoSomething("Work");
}
}
public class LoggingAttribute : MoAttribute
{
// 自动捕获方法上下文
public override void OnEntry(MethodContext context)
{
Console.WriteLine($"Entering {context.Method.Name} with args: {string.Join(",", context.Arguments)}");
}
public override void OnExit(MethodContext context)
{
Console.WriteLine($"Exiting {context.Method.Name}, return: {context.ReturnValue}");
}
public override void OnException(MethodContext context)
{
Console.WriteLine($"Error in {context.Method.Name}: {context.Exception.Message}");
}
}
// 应用特性(无需修改Service类)
public class Service
{
[Logging]
public void DoSomething(string something)
{
Console.WriteLine($"Doing {something}...");
}
}
运行结果:
Entering DoSomething with args: Work
Doing Work...
Exiting DoSomething, return:
特性可以用于性能监控。例如,你可以使用特性标记需要监控的方法,并在运行时收集性能数据。
using System;
using System.Diagnostics;
public class PerformanceMonitorAttribute : Attribute
{
public void OnInvoke(Action action)
{
var stopwatch = Stopwatch.StartNew();
action();
stopwatch.Stop();
Console.WriteLine($"Execution time: {stopwatch.ElapsedMilliseconds} ms");
}
}
public class PerformanceService
{
[PerformanceMonitor]
public void PerformTask()
{
// 模拟耗时操作
System.Threading.Thread.Sleep(1000);
}
}
class Program
{
static void Main(string[] args)
{
var performanceService = new PerformanceService();
var method = performanceService.GetType().GetMethod(nameof(PerformanceService.PerformTask));
var attribute = (PerformanceMonitorAttribute)method.GetCustomAttributes(typeof(PerformanceMonitorAttribute), false).FirstOrDefault();
if (attribute != null)
{
attribute.OnInvoke(() => performanceService.PerformTask());
}
}
}
MethodTimer.Fody
internal class Program
{
static void Main(string[] args)
{
new Program().SayHi();
Console.WriteLine("测试方法执行结束");
Console.ReadKey();
}
[Time]
public void SayHi()
{
Console.WriteLine("Hi");
}
}
MyCustomAttribute
而非 MyAttr
)。CustomAttribute
),特性类名必须以Attribute
结尾(如[Author]
对应AuthorAttribute
)int
、string
)、枚举或 Type
。[DataContract]
)的使用场景。维度 | 特性(Attribute) | 注释(Comment) |
---|---|---|
存储位置 | 编译到元数据 | 仅存在于源代码 |
可访问性 | 运行时通过反射读取 | 开发时查看 |
功能影响 | 可影响编译/运行行为 | 纯说明性 |
使用场景 | 框架集成、代码分析、AOP | 代码可读性 |
///
)。C# 特性是元数据编程的利器,既能简化代码结构,又能实现高度灵活的运行时行为控制。合理利用特性,可以让代码像乐高积木一样模块化,释放声明式编程的无限可能!
C#特性是元数据管理的强大工具,其核心价值在于:
[Obsolete]
)提升代码质量。掌握特性的定义、使用场景和高级应用,开发者可以:
回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
C#指南 - 特性
MSDN文档 特性
Attribute 类
C#基础知识学习 之 ☀️ 特性(Attribute) 的含义和用法
C# 特性(Attribute)
Microsoft Docs: Attributes