C# 特性(Attributes)使用详解

总目录

前言

在 C# 中,特性(Attributes)是一种用于向代码添加元数据的强大机制。这些元数据可以被编译器、运行时环境或开发工具读取,并用于控制程序的行为。本文将详细介绍C#中特性的基本概念、常见用法以及一些高级应用。


一、什么是特性?

1. 定义

  • 特性(Attribute)是 C# 中用于向代码元素(类、方法、属性等)附加元数据(Metadata)的声明性标记,本质是继承自 System.Attribute 的类。
  • 通过特性,开发者可以在编译时或运行时为代码附加额外信息,这些信息可以在编译时或运行时被读取和处理。从而影响程序的行为或提供辅助功能。

2. 语法

特性使用方括号[]表示,通常放置在它们所修饰的元素之前。例如:

[Serializable]
public class MyClass
{
    // 类的定义
}

在这个例子中,[Serializable] 是一个特性,它表示 MyClass 可以被序列化。

3. 作用

  • 元数据扩展:为代码添加额外的描述信息(如版本、作者、功能说明),这些信息可通过反射在运行时读取。
  • 行为控制:影响编译器或运行时的操作(如序列化规则、安全权限、条件编译)。
  • 代码解耦:通过声明式编程将非业务逻辑(如日志、权限)与核心代码分离。
  • 框架集成:ASP.NET、Entity Framework 等框架依赖特性实现功能(如路由、序列化)。
  • 编译时检查:通过 [Obsolete] 等特性标记代码问题。

二、内置特性

C# 提供了许多内置特性,涵盖了从序列化到性能优化的各种用途。下面介绍一些常用的内置特性。

1. 基本特性

1)[Serializable]

序列化控制:用于标记一个类可以被序列化。这意味着该类的对象可以被转换为字节流,以便于存储或传输。

[Serializable]
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

2)[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() { }  // 引发编译错误

编译时触发警告或错误提示,强制代码升级。

3)[Conditional]

条件编译:用于标记一个方法,使其只有在特定条件满足时才会被调用。最常用的是 DEBUGTRACE 条件。

using System.Diagnostics;

public class Logger
{
    [Conditional("DEBUG")]
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

仅在定义了 DEBUG 符号时编译和执行该方法,适合调试日志。

4)[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);
    }
}

5)[Flags]

枚举位标志[Flags]特性用于表示枚举类型是位标志,支持位运算。

[Flags]
public enum Permissions 
{
    None = 0,
    Read = 1,
    Write = 2,
    All = Read | Write
}

6)[STAThread]

[STAThread]特性用于指定线程的Apartment State,适用于Windows Forms应用程序。

[STAThread]
public static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

7)[MethodImpl]

[MethodImpl]特性用于控制方法的实现方式,如内联建议。

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Add(int a, int b)
{
    return a + b;
}

2. 元数据特性

1)[assembly: AssemblyTitle]

用于设置程序集的标题。

[assembly: AssemblyTitle("MyApplication")]

2)[assembly: AssemblyDescription]

用于设置程序集的描述。

[assembly: AssemblyDescription("This is a sample application.")]

3)[assembly: AssemblyVersion]

用于设置程序集的版本号。

[assembly: AssemblyVersion("1.0.0.0")]

4)[assembly: AssemblyFileVersion]

用于设置程序集的文件版本号。

[assembly: AssemblyFileVersion("1.0.0.0")]

3. 其他常用特性

1)[XmlRoot]

用于指定XML序列化时的根元素名称。

[XmlRoot("Person")]
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

2)[XmlElement]

用于指定XML序列化时的元素名称。

public class Person
{
    [XmlElement("FullName")]
    public string Name { get; set; }
    [XmlElement("Years")]
    public int Age { get; set; }
}

3)[XmlAttribute]

用于指定XML序列化时的属性名称。

public class Person
{
    [XmlAttribute("name")]
    public string Name { get; set; }
    [XmlAttribute("age")]
    public int Age { get; set; }
}

4)[JsonIgnore]

用于指定序列化时忽略某个属性,常用于JSON序列化。

public class Person
{
    public string Name { get; set; }
    [JsonIgnore]
    public int Age { get; set; }
}

5)[JsonProperty]

用于指定JSON序列化时的属性名称。

public class Person
{
    [JsonProperty("full_name")]
    public string Name { get; set; }
    [JsonProperty("years")]
    public int Age { get; set; }
}

三、自定义特性

除了使用内置特性外,你还可以创建自己的特性。自定义特性可以帮助你在项目中实现特定的需求。开发者自行定义,用于扩展元数据,如权限控制、日志标记等。

1. 创建自定义特性

1) 定义特性

要创建自定义特性,你需要继承 Attribute 类并定义所需的构造函数和属性。

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CustomAttribute : Attribute
{
    public string Description { get; }

    public CustomAttribute(string description)
    {
        Description = description;
    }
}

2)特性参数详解

可以通过继承Attribute类定义自定义特性,并使用[AttributeUsage]指定其适用范围。

AttributeUsage:指定特性可以应用于哪些目标(如类、方法等),并设置是否允许多次应用。

  • AttributeTargets:指定适用目标(类/方法/属性等)
  • AllowMultiple:是否允许多次应用
  • Inherited:是否被派生类继承
[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("张三")])。

3)特性的作用范围

通过使用AttributeUsageAttribute,可以指定自定义特性可以应用的作用范围。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyCustomAttribute : Attribute
{
    // 特性的定义
}
  • AttributeTargets.Class:表示特性可以应用于类。
  • AttributeTargets.Method:表示特性可以应用于方法。
  • 其他可能的值包括AttributeTargets.PropertyAttributeTargets.Field等。

2. 使用自定义特性

一旦定义了自定义特性,你就可以像使用内置特性一样使用它。

[Custom("This is a custom attribute for the class.")]
public class MyClass
{
    [Custom("This is a custom attribute for the method.")]
    public void MyMethod()
    {
        // 方法体
    }
}

3. 反射读取特性

你可以使用反射来读取特性信息。以下是一个简单的示例:

[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.
        }
    }
}

4. 自定义特性完整示例

1)定义自定义特性

继承 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;
    }
}

2)使用自定义特性

public class Product 
{
    [MyCustom("产品名称", 1)]
    public string Name { get; set; }

    [MyCustom("价格", 2)]
    public decimal Price { get; set; }
}

3)通过反射读取特性

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

四、特性在实际项目中的应用

特性不仅可以在日常编码中简化工作,还可以在大型项目中发挥重要作用。以下是几个应用场景。

1. 序列化与反序列化

在进行对象的序列化和反序列化时,可以使用特性来控制哪些字段或属性需要被序列化。

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; }
}

2. 数据验证

你可以使用特性来进行数据验证。例如,在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; }
}

3. 动态数据验证

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} 值超出范围");
        }
    }
}

4. API 控制器和操作方法(Web API配置)

在构建 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)
    {
        // 创建新用户
    }
}

5. 面向切面编程(AOP):日志记录

通过特性结合AOP(面向切面编程)技术,可以实现自动化的日志记录功能。这样可以减少重复代码,提高代码的可维护性。

1)手动实现

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

2)第三方框架

  • 安装NuGet包 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:

6. 性能监控

特性可以用于性能监控。例如,你可以使用特性标记需要监控的方法,并在运行时收集性能数据。

1) 手动实现

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());
        }
    }
}

2)第三方框架

  • 引入NuGet包 MethodTimer.Fody
    internal class Program
    {
        static void Main(string[] args)
        {
            new Program().SayHi();
            Console.WriteLine("测试方法执行结束");
            Console.ReadKey();
        }

        [Time]
        public void SayHi()
        {
            Console.WriteLine("Hi");
        }
    }

C# 特性(Attributes)使用详解_第1张图片

五、最佳实践与注意事项

1. 设计原则

  • 单一职责:每个特性应只描述一个功能。
  • 可读性:命名清晰,参数直观(如 MyCustomAttribute 而非 MyAttr)。
  • 性能:避免在高频方法上使用复杂的特性逻辑。
  • 命名规范 : 遵循 Pascal 命名法(如 CustomAttribute),特性类名必须以Attribute结尾(如[Author]对应AuthorAttribute
  • 版本兼容:修改特性参数时需考虑旧版本代码的兼容性以及自定义特性需考虑跨平台支持(如.NET Core vs.NET Framework)。

2. 避免常见错误

  • 密封自定义特性类:防止被继承导致意外行为。
  • 参数类型限制:特性参数需为简单类型(如 intstring)、枚举或 Type
  • 反射性能:频繁读取特性可能影响性能,高频场景建议缓存结果。
  • 避免过度使用:特性会增加代码复杂度,避免滥用,仅用于必要场景(如框架开发、配置管理)。优先用常规设计模式解决问题。

3. 与第三方框架的兼容性

  • 遵循公共语言规范(CLS):确保特性在跨语言项目中兼容。
  • 避免冲突:检查框架内置特性(如 [DataContract])的使用场景。

六、特性 vs 注释

维度 特性(Attribute) 注释(Comment)
存储位置 编译到元数据 仅存在于源代码
可访问性 运行时通过反射读取 开发时查看
功能影响 可影响编译/运行行为 纯说明性
使用场景 框架集成、代码分析、AOP 代码可读性
  • 注释:用于人类阅读,不参与编译或运行(如/// )。
  • 特性:编译时嵌入元数据,可被反射或框架使用,直接影响程序行为。

七、总结

C# 特性是元数据编程的利器,既能简化代码结构,又能实现高度灵活的运行时行为控制。合理利用特性,可以让代码像乐高积木一样模块化,释放声明式编程的无限可能!

C#特性是元数据管理的强大工具,其核心价值在于:

  1. 解耦逻辑:通过元数据分离业务逻辑与框架行为。
  2. 扩展性:通过自定义特性实现灵活的运行时控制。
  3. 可维护性:通过内置特性(如 [Obsolete])提升代码质量。

掌握特性的定义、使用场景和高级应用,开发者可以:

  • 简化代码:减少样板代码,通过元数据驱动逻辑。
  • 增强框架集成:如ASP.NET Core中路由、模型绑定等。
  • 实现AOP模式:日志、权限等横切关注点的统一管理。

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
C#指南 - 特性
MSDN文档 特性
Attribute 类
C#基础知识学习 之 ☀️ 特性(Attribute) 的含义和用法
C# 特性(Attribute)
Microsoft Docs: Attributes

你可能感兴趣的:(C#,c#,开发语言,C#,知识捡漏)