Bootstrap

【C#】反射 和 特性(Attribute)、[AttributeUsage(AttributeTargets.Property)]

反射(Reflection)和特性(Attribute)

是 C# 中两个非常强大的功能,它们常常一起使用,尤其在需要动态检查和操作类型时。下


1. 反射(Reflection)

反射是 C# 中的一种机制,允许程序在运行时动态地检查和操作对象的类型、方法、属性、字段等元数据。

反射提供了一种方法,可以在运行时获取关于类型的详细信息,如类名、方法名、字段、属性类型等。使用反射,程序能够在不知道对象类型的情况下,操作和调用它的成员。

反射常见用途:
  • 动态调用方法、构造函数和属性
  • 获取类型信息,如类的名称、成员(方法、属性、字段等)
  • 检查特性(Attribute)是否应用于某个成员
  • 实现动态加载程序集
反射常见类与接口:
  • Type:用于获取类型信息,如获取类名、字段、属性、方法等。
  • Assembly:用于加载程序集并获取程序集中的类型信息。
  • MethodInfoPropertyInfoFieldInfo:用于表示类型的成员(方法、属性、字段等)信息。
  • Activator:用于动态创建对象的实例。
示例:反射的基本用法
using System;
using System.Reflection;

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

    public void SayHello()
    {
        Console.WriteLine($"Hello, my name is {Name} and I am {Age} years old.");
    }
}

class Program
{
    static void Main()
    {
        // 获取类型信息
        Type personType = typeof(Person);
        
        // 获取属性信息
        PropertyInfo nameProperty = personType.GetProperty("Name");
        PropertyInfo ageProperty = personType.GetProperty("Age");
        
        // 获取方法信息
        MethodInfo sayHelloMethod = personType.GetMethod("SayHello");
        
        // 创建实例
        object personInstance = Activator.CreateInstance(personType);
        
        // 使用反射设置属性值
        nameProperty.SetValue(personInstance, "John");
        ageProperty.SetValue(personInstance, 30);
        
        // 调用方法
        sayHelloMethod.Invoke(personInstance, null);
    }
}

在上面的代码中:

  • 使用 Type.GetProperty() 获取属性信息。
  • 使用 Type.GetMethod() 获取方法信息。
  • 使用 Activator.CreateInstance() 动态创建对象。
  • 使用 PropertyInfo.SetValue()MethodInfo.Invoke() 来操作对象的属性和方法。

2. 特性(Attribute)

特性(Attribute)是 C# 中的一个元数据构造,用来为程序的元素(如类、方法、属性、字段等)附加元数据。特性本质上是附加到程序元素上的标记,用于提供附加信息。

特性可以用于:

  • 标记类或方法,以便于后续的逻辑处理。
  • 指定方法的行为,如 Obsolete 特性表示某个方法已过时。
  • 用于序列化、验证、权限控制等方面。
特性工作原理

特性本质上是一种类,继承自 System.Attribute 类,可以附加到类、方法、属性、字段等代码元素上。特性本身不会直接影响程序的执行,但可以通过反射或其他方式读取特性,进而改变程序的行为。

示例:定义和使用特性
using System;

// 定义一个自定义特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DeveloperInfoAttribute : Attribute
{
    public string DeveloperName { get; }
    public string Description { get; }

    public DeveloperInfoAttribute(string developerName, string description)
    {
        DeveloperName = developerName;
        Description = description;
    }
}

// 应用自定义特性
[DeveloperInfo("John Doe", "This class represents a person.")]
public class Person
{
    [DeveloperInfo("Jane Smith", "This method prints a greeting.")]
    public void Greet()
    {
        Console.WriteLine("Hello!");
    }
}

class Program
{
    static void Main()
    {
        // 获取类上的特性
        var classAttributes = typeof(Person).GetCustomAttributes(typeof(DeveloperInfoAttribute), false);
        foreach (var attr in classAttributes)
        {
            var devInfo = (DeveloperInfoAttribute)attr;
            Console.WriteLine($"Class Developer: {devInfo.DeveloperName}, Description: {devInfo.Description}");
        }
        
        // 获取方法上的特性
        var methodAttributes = typeof(Person).GetMethod("Greet").GetCustomAttributes(typeof(DeveloperInfoAttribute), false);
        foreach (var attr in methodAttributes)
        {
            var devInfo = (DeveloperInfoAttribute)attr;
            Console.WriteLine($"Method Developer: {devInfo.DeveloperName}, Description: {devInfo.Description}");
        }
    }
}

在上面的代码中:

  • 定义了一个 DeveloperInfoAttribute 特性,它包含开发者姓名和描述信息。
  • 将该特性应用于类 Person 和它的方法 Greet 上。
  • 通过反射(GetCustomAttributes)读取类和方法上的特性信息。

3. 反射与特性结合的实际应用

反射和特性常常结合使用,特别是在框架开发、ORM(对象关系映射)、序列化等场景中。常见的场景有:

  • 自定义序列化/反序列化:通过特性标记类或属性,反射读取这些特性来决定如何序列化或反序列化数据。
  • ORM 框架:通过特性标记数据库表和字段,通过反射映射到类和属性,进行数据库操作。
  • ASP.NET MVC/Web API:使用特性来标记控制器和方法,例如 [HttpGet][Route] 等,路由和方法的映射依赖反射来确定。
示例:反射读取特性实现序列化
using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Property)]
public class JsonPropertyAttribute : Attribute
{
    public string Name { get; }
    public JsonPropertyAttribute(string name) => Name = name;
}

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

    [JsonProperty("age_in_years")]
    public int Age { get; set; }
}

class Program
{
    static void Main()
    {
        var person = new Person { Name = "John", Age = 30 };

        // 动态序列化为 JSON
        var json = SerializeToJson(person);
        Console.WriteLine(json);
    }

    static string SerializeToJson(object obj)
    {
        var properties = obj.GetType().GetProperties();
        var jsonParts = new System.Text.StringBuilder();
        jsonParts.Append("{");

        foreach (var prop in properties)
        {
            var jsonAttr = (JsonPropertyAttribute)Attribute.GetCustomAttribute(prop, typeof(JsonPropertyAttribute));
            if (jsonAttr != null)
            {
                jsonParts.Append($"\"{jsonAttr.Name}\": \"{prop.GetValue(obj)}\",");
            }
        }

        // 去掉最后一个逗号
        if (jsonParts[jsonParts.Length - 1] == ',')
        {
            jsonParts.Remove(jsonParts.Length - 1, 1);
        }

        jsonParts.Append("}");
        return jsonParts.ToString();
    }
}

这个示例中,通过 JsonPropertyAttribute 特性来标记类属性,然后用反射读取这些特性实现了一个简单的动态 JSON 序列化。


小结

  • 反射 允许我们在运行时获取和操作类型信息,动态地检查类型和其成员。
  • 特性(Attribute) 用于为程序的元素附加元数据,通常在编译时不直接影响程序行为,但可以通过反射或其他机制读取并影响程序执行。

[AttributeUsage(AttributeTargets.Property)]

[AttributeUsage(AttributeTargets.Property)] 是 C# 中的一种特性(Attribute),用于指定自定义特性(Attribute)可以应用的目标类型。它属于 反射特性(Attribute) 相关。

1. 含义

[AttributeUsage(AttributeTargets.Property)] 指定了一个自定义特性(Attribute)可以应用到 属性(Property)上。也就是说,定义这个特性时,我们使用 AttributeUsage 来约束它只能应用于属性。

  • AttributeTargets.PropertyAttributeTargets 枚举的一个成员,表示该特性只能应用于属性(Property)。

2. 常见用法

在 C# 中,可以创建自定义特性并使用 [AttributeUsage] 来限定该特性应用的范围。比如,创建一个只能应用于属性的自定义特性。

3. 示例

有一个自定义特性 MyCustomAttribute,并且想要让它只能应用到类的属性上。可以这样做:

using System;

// 定义一个只能应用于属性的自定义特性
[AttributeUsage(AttributeTargets.Property)]
public class MyCustomAttribute : Attribute
{
    public string Description { get; }

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

// 使用 MyCustomAttribute 特性应用到属性上
public class Person
{
    [MyCustomAttribute("This is the person's name")]
    public string Name { get; set; }

    [MyCustomAttribute("This is the person's age")]
    public int Age { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var person = new Person { Name = "John", Age = 30 };

        // 使用反射获取属性上的特性
        var properties = typeof(Person).GetProperties();
        foreach (var property in properties)
        {
            var attribute = (MyCustomAttribute)Attribute.GetCustomAttribute(property, typeof(MyCustomAttribute));
            if (attribute != null)
            {
                Console.WriteLine($"{property.Name}: {attribute.Description}");
            }
        }
    }
}

4. 解释

  • AttributeUsage(AttributeTargets.Property):表示 MyCustomAttribute 特性只能应用于 属性 上。这样,如果你尝试将 MyCustomAttribute 应用到方法、类或其他地方,就会得到编译错误。

  • AttributeTargets.Property:这是 AttributeTargets 枚举的一部分,表示该特性可以应用于 属性AttributeTargets 枚举还包含其他选项,如 ClassMethodField 等,表示该特性可以应用的目标。

5. AttributeUsage 枚举

AttributeUsage 特性有一个参数 AttributeTargets,它是一个枚举类型,用于指定该特性可以应用的目标,常见的值包括:

  • AttributeTargets.Class:类
  • AttributeTargets.Method:方法
  • AttributeTargets.Property:属性
  • AttributeTargets.Field:字段
  • AttributeTargets.Parameter:方法参数
  • AttributeTargets.Event:事件
  • AttributeTargets.All:所有目标类型

可以通过设置不同的 AttributeTargets 值来控制特性的使用范围。

6. 小结

[AttributeUsage(AttributeTargets.Property)] 用来限制特性(Attribute)的应用目标,使得该特性只能应用到属性上。在创建自定义特性时,使用此注解帮助开发者理解特性的使用场景,并通过反射获取相关的元数据。

;