1. C#特性概述
C#特性(Attributes)是.NET框架中一种强大的元数据机制,它允许开发者为程序集、类型、成员等元素添加声明性信息。这些信息可以在运行时通过反射机制获取,从而实现各种高级功能。特性本质上是从System.Attribute类派生的特殊类,它们为代码添加了额外的语义层。
在C#中,特性通常以方括号[]包裹并放置在目标元素的上方。例如:
csharp复制[Serializable]
public class MyClass
{
[Obsolete("该方法已弃用,请使用NewMethod代替")]
public void OldMethod() { }
}
特性在.NET生态系统中扮演着关键角色,广泛应用于序列化、Web服务、单元测试、安全控制等领域。它们提供了一种优雅的方式来扩展代码行为,而无需修改原始实现逻辑。
2. 内置常用特性详解
2.1 序列化相关特性
Serializable特性是最常用的内置特性之一,它标记一个类可以被序列化:
csharp复制[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
[NonSerialized]
private string _secretInfo; // 这个字段不会被序列化
}
JsonIgnore特性在System.Text.Json命名空间中,用于控制JSON序列化:
csharp复制public class Product
{
public int Id { get; set; }
[JsonIgnore]
public decimal InternalDiscount { get; set; } // 不会出现在JSON输出中
}
2.2 过时方法标记
Obsolete特性用于标记不再推荐使用的代码元素:
csharp复制public class Calculator
{
[Obsolete("请使用Add方法替代", true)] // 第二个参数设为true将导致编译错误
public int Sum(int a, int b) => a + b;
public int Add(int a, int b) => a + b;
}
2.3 条件编译特性
Conditional特性常用于调试和日志记录:
csharp复制public class Logger
{
[Conditional("DEBUG")]
public static void Log(string message)
{
Console.WriteLine($"[DEBUG] {DateTime.Now}: {message}");
}
}
3. 创建自定义特性
3.1 基本自定义特性实现
创建自定义特性需要继承System.Attribute类:
csharp复制[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
public class AuthorAttribute : Attribute
{
public string Name { get; }
public string Version { get; set; }
public AuthorAttribute(string name)
{
Name = name;
}
}
AttributeUsage特性控制自定义特性的使用方式:
- AttributeTargets:指定特性可以应用的目标(类、方法、属性等)
- AllowMultiple:是否允许在同一目标上多次应用
- Inherited:是否可被派生类继承
3.2 带参数的自定义特性
自定义特性可以包含属性和构造函数参数:
csharp复制[AttributeUsage(AttributeTargets.Property)]
public class RangeAttribute : Attribute
{
public double Min { get; }
public double Max { get; }
public RangeAttribute(double min, double max)
{
Min = min;
Max = max;
}
}
public class Product
{
[Range(0, 100)]
public decimal Discount { get; set; }
}
4. 特性的高级应用
4.1 通过反射获取特性信息
运行时可以通过反射读取特性信息:
csharp复制var type = typeof(Product);
var properties = type.GetProperties();
foreach (var prop in properties)
{
var rangeAttr = prop.GetCustomAttribute<RangeAttribute>();
if (rangeAttr != null)
{
Console.WriteLine($"{prop.Name} 的有效范围: {rangeAttr.Min}-{rangeAttr.Max}");
}
}
4.2 特性在ASP.NET Core中的应用
ASP.NET Core广泛使用特性进行路由、验证等:
csharp复制[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
// 实现代码
}
[HttpPost]
[Authorize(Roles = "Admin")]
public IActionResult Create([FromBody] Product product)
{
// 实现代码
}
}
4.3 特性在单元测试中的应用
xUnit等测试框架大量使用特性:
csharp复制public class CalculatorTests
{
[Fact]
public void Add_TwoNumbers_ReturnsSum()
{
// 测试代码
}
[Theory]
[InlineData(1, 2, 3)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
public void Add_VariousInputs_ReturnsCorrectSum(int a, int b, int expected)
{
// 测试代码
}
}
5. 性能考量与最佳实践
5.1 特性对性能的影响
虽然特性本身不会直接影响代码执行性能,但反射操作可能带来开销:
- 尽量减少运行时频繁查询特性
- 考虑缓存反射结果
- 对于高性能场景,可以使用表达式树或源代码生成替代反射
5.2 特性使用的最佳实践
- 命名规范:自定义特性类名应以"Attribute"结尾,使用时可以省略
- 目标明确:使用AttributeUsage精确控制特性的适用范围
- 避免滥用:只在确实需要元数据的场景使用特性
- 文档说明:为自定义特性添加清晰的XML注释
- 线程安全:确保特性类的实现是线程安全的
5.3 特性与AOP编程
特性常被用于实现面向切面编程(AOP):
csharp复制[LoggingAspect]
[TransactionAspect]
public class OrderService : IOrderService
{
[CacheAspect(Duration = 60)]
public Order GetOrder(int id)
{
// 实现代码
}
}
通过特性实现的AOP可以将横切关注点(如日志、缓存、事务)与业务逻辑分离,提高代码的可维护性。
