WPF 中的 特性 介绍

146 阅读4分钟

在代码中,中括号 [...] 包裹的内容被称为 特性(Attribute) 。特性是.NET中的一种元数据机制,用于向代码添加声明性信息。它们可以在类、方法、属性、字段等程序元素上使用,并在运行时通过反射(Reflection)来读取这些信息。

在代码:

[BindingProperty(Target = "newMessage")]
public string NewMessage

[BindingProperty(Target = "newMessage")] 是一个特性,它为 NewMessage 属性提供了额外的元数据信息。

特性的作用

特性的作用通常取决于它所定义的类。在这个例子中,BindingProperty 特性可能是自定义的,用于指定某种绑定关系或其他行为。具体来说:

  1. BindingProperty 特性

    • 这是一个自定义特性,可能用于定义数据绑定的规则或目标。
    • 它可能用于指示 NewMessage 属性应该绑定到某个特定的目标(如 UI 控件或其他数据源)。
  2. Target = "newMessage"

    • 这是特性的一个参数,指定了绑定的目标。
    • 在这个例子中,Target 参数的值是字符串 "newMessage",可能表示 NewMessage 属性应该绑定到名为 newMessage 的目标。

特性的作用范围

特性可以应用于以下范围:

  • 类、结构体、枚举:为整个类型添加元数据。
  • 属性、字段:为成员变量或属性添加元数据。
  • 方法:为方法添加元数据,例如 [Obsolete] 表示方法已废弃。
  • 参数:为方法的参数添加元数据。

示例:常见的特性

以下是一些常见的特性及其用途:

  1. [Obsolete]

    [Obsolete("This method is deprecated. Use NewMethod instead.")]
    public void OldMethod()
    {
        // ...
    }
    
    • 标记方法为已废弃,编译器会发出警告。
  2. [Serializable]

    [Serializable]
    public class MyClass
    {
        // ...
    }
    
    • 表示类可以被序列化。
  3. [JsonProperty] (来自 JSON.NET):

    public class Person
    {
        [JsonProperty("name")]
        public string Name { get; set; }
    }
    
    • 指定 JSON 序列化时的属性名称。
  4. [DisplayName] (来自 System.ComponentModel):

    public class Product
    {
        [DisplayName("Product Name")]
        public string Name { get; set; }
    }
    
    • 为属性指定显示名称,常用于数据注解。

自定义特性

BindingProperty 特性可能是自定义的,它的具体行为取决于它的定义。例如:

[AttributeUsage(AttributeTargets.Property)]
public class BindingPropertyAttribute : Attribute
{
    public string Target { get; }

    public BindingPropertyAttribute(string target)
    {
        Target = target;
    }
}
  • 这个特性定义了一个 Target 属性,用于存储绑定目标。
  • 它可以应用于属性(AttributeTargets.Property)。

总结

[BindingProperty(Target = "newMessage")]
public string NewMessage
  • [BindingProperty(Target = "newMessage")] 是一个特性,用于为 NewMessage 属性添加元数据。
  • Target = "newMessage" 指定了绑定的目标。
  • 特性的具体行为取决于它的定义,可能是用于数据绑定或其他自定义逻辑。

特性是.NET中一种强大的机制,用于在代码中添加额外的元数据,这些元数据可以在运行时通过反射读取和处理。

手写一个自定义特性并使用

好的!我们可以通过一个简单的例子来展示如何自定义一个特性,并将其应用到代码中。然后,我会展示如何用不使用特性的方式实现相同的功能。

场景:验证属性是否为空

假设我们有一个需求:在类的属性上标记一个特性,用于验证该属性的值是否为空。如果为空,抛出一个异常。

1. 自定义特性

首先,我们定义一个自定义特性 NotEmptyAttribute,用于标记属性是否可以为空。

csharp复制

using System;

// 定义特性,只能应用于属性
[AttributeUsage(AttributeTargets.Property)]
public class NotEmptyAttribute : Attribute
{
    // 特性可以包含一些参数,比如错误消息
    public string ErrorMessage { get; }

    public NotEmptyAttribute(string errorMessage = "该属性不能为空")
    {
        ErrorMessage = errorMessage;
    }
}

2. 使用自定义特性

接下来,我们定义一个类,并在属性上使用这个特性。

csharp复制

public class Person
{
    [NotEmpty("姓名不能为空")]
    public string Name { get; set; }

    [NotEmpty("年龄不能为空")]
    public int Age { get; set; }
}

3. 验证逻辑

为了实现验证逻辑,我们需要在运行时通过反射检查属性上是否有 NotEmptyAttribute,并执行相应的验证。

csharp复制

using System.Reflection;

public static class Validator
{
    public static void Validate(object obj)
    {
        // 获取对象的所有属性
        var properties = obj.GetType().GetProperties();

        foreach (var property in properties)
        {
            // 检查属性是否有NotEmptyAttribute
            var attribute = property.GetCustomAttribute<NotEmptyAttribute>();
            if (attribute != null)
            {
                // 获取属性的值
                var value = property.GetValue(obj);

                // 如果值为空,抛出异常
                if (value == null || (value is string str && string.IsNullOrWhiteSpace(str)))
                {
                    throw new ArgumentException(attribute.ErrorMessage, property.Name);
                }
            }
        }
    }
}

4. 测试代码

csharp复制

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

        try
        {
            Validator.Validate(person);
            Console.WriteLine("验证通过!");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"验证失败:{ex.Message}");
        }
    }
}

等价的不使用特性的代码

如果我们不使用特性,就需要手动实现验证逻辑。以下是等价的代码:

1. 定义类

csharp复制

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

2. 手动验证逻辑

csharp复制

public static class Validator
{
    public static void Validate(Person person)
    {
        if (string.IsNullOrWhiteSpace(person.Name))
        {
            throw new ArgumentException("姓名不能为空", nameof(person.Name));
        }

        if (person.Age == 0)
        {
            throw new ArgumentException("年龄不能为空", nameof(person.Age));
        }
    }
}

3. 测试代码

csharp复制

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

        try
        {
            Validator.Validate(person);
            Console.WriteLine("验证通过!");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"验证失败:{ex.Message}");
        }
    }
}

对比

  • 使用特性

    • 优点:代码更简洁,验证逻辑与业务逻辑分离,易于扩展和维护。
    • 缺点:需要反射,性能稍差,且需要额外的验证逻辑。
  • 不使用特性

    • 优点:性能更好,逻辑更直观。
    • 缺点:代码重复,难以扩展,验证逻辑与业务逻辑耦合。

总结

特性是一种强大的工具,可以将验证逻辑、元数据等信息与业务逻辑分离,使代码更加清晰和易于维护。然而,特性需要通过反射来实现,可能会带来一定的性能开销。在实际开发中,可以根据需求选择是否使用特性。