【代码设计】C#特性Attribute与自动化

2015年09月07日 11:50 0 点赞 0 评论 更新于 2025-11-21 18:57

作者:卡卡西0旗木

泰斗链接:点击查看

本文将详细介绍C#中Attribute的使用方法及其常见应用场景。例如,将玩家的血量、攻击、防御等属性存储在枚举中,界面的多个位置可能需要根据该枚举获取属性的描述文本;在网络框架开发中,一个协议号对应一个类的处理方法;在ORM(对象关系映射)中,一个类的属性是否映射到持久化文件中的属性,以及映射后的属性名是什么等。

1、什么是Attribute

如果您熟悉Java的Annotation,可以将Attribute视为与之类似的概念。对于尚不了解Attribute的读者,我们来看一下官方的解释:

Attribute class associates predefined system information or user - defined custom information with a target element. A target element can be an assembly, class, constructor, delegate, enum, event, field, interface, method, portable executable file module, parameter, property, return value, struct, or another attribute. Information provided by an attribute is also known as metadata. Metadata can be examined at run time by your application to control how your program processes data, or before run time by external tools to control how your application itself is processed or maintained. For example, the .NET Framework predefines and uses attribute types to control run - time behavior, and some programming languages use attribute types to represent language features not directly supported by the .NET Framework common type system. Attribute class. Attributes can be applied to any target element; multiple attributes can be applied to the same target element; and attributes can be inherited by an element derived from a target element. AttributeTargets class to specify the target element to which the attribute is applied. Attribute class provides convenient methods to retrieve and test custom attributes. Applying Attributes and Extending Metadata Using Attributes.

翻译如下: Attribute类可将目标元素与预定义的系统信息或用户自定义的信息关联起来。这里的目标元素可以是程序集(assembly)、类(class)、构造函数(constructor)、委托(delegate)、枚举(enum)、事件(event)、字段(field)、接口(interface)、方法(method)、可执行文件模块、参数(parameter)、属性(property)、返回值(return value)、结构体(struct)或其他Attribute。

Attribute提供的信息也被称为元数据(metadata)。元数据可在运行时由应用程序检查,以控制程序如何处理数据;也可在运行前由外部工具检查,以控制应用程序本身的处理或维护方式。例如,.NET Framework预定义并使用Attribute类型来控制运行时行为,一些编程语言使用Attribute类型来表示.NET Framework通用类型系统不直接支持的语言特性。

所有的Attribute类型都直接或间接继承自Attribute类。Attribute可以应用于任何目标元素;多个Attribute可以应用于同一个目标元素;并且Attribute可以被从目标元素派生的元素继承。可以使用AttributeTargets类来指定Attribute应用的目标元素。Attribute类提供了便捷的方法来检索和测试自定义Attribute。更多关于Attribute的信息,请参考“Applying Attributes”和“Extending Metadata Using Attributes”。

如果您对官方解释和翻译仍有疑惑,没关系,接下来我们通过一个例子来展示Attribute的作用。

2、用Attribute将枚举和一个描述文本绑定在一起

假设有如下枚举:

public enum Properties
{
/// <summary>
/// 血量
/// </summary>
HP = 1,

/// <summary>
/// 物理攻击
/// </summary>
PhyAtk = 2,

/// <summary>
/// 物理防御
/// </summary>
PhyDef = 3,

/// <summary>
/// 法术攻击
/// </summary>
MagAtk = 4,

/// <summary>
/// 法术防御
/// </summary>
MagDef = 5
}

注意:如果包含中文的代码编译时出现“Newline in constant”错误,请将文件编码保存为“带BOM的UTF - 8”。在VS中,可通过“文件” - “高级保存选项”,然后在编码下拉列表中选择。

现在,我们希望根据枚举值获取其中文描述,例如传入Properties.MagDef返回“法术防御”。最原始的做法如下:

public class PropertiesUtils
{
public static string GetDescByProperties(Properties p)
{
switch (p)
{
case Properties.HP:
return "血量";
case Properties.PhyAtk:
return "物理攻击";
case Properties.PhyDef:
return "物理防御";
case Properties.MagAtk:
return "法术攻击";
case Properties.MagDef:
return "法术防御";
default:
return "未知属性:" + p;
}
}
}

这种方法虽然可以解决问题,但我们可以使用Attribute来优化。首先,定义一个用于存储描述文本的Attribute:

[System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Enum)]
public class PropertiesDesc : System.Attribute
{
public string Desc { get; private set; }

public PropertiesDesc(string desc)
{
Desc = desc;
}
}

然后,将上面定义的PropertiesDesc应用到Properties枚举上:

public enum Properties
{
[PropertiesDesc("血量")]
HP = 1,

[PropertiesDesc("物理攻击")]
PhyAtk = 2,

[PropertiesDesc("物理防御")]
PhyDef = 3,

[PropertiesDesc("法术攻击")]
MagAtk = 4,

[PropertiesDesc("法术防御")]
MagDef = 5
}

这样,我们就通过Attribute将文本描述信息关联到了枚举属性上。接下来,重写PropertiesUtils类来获取描述信息:

public class PropertiesUtils
{
public static string GetDescByProperties(Properties p)
{
Type type = p.GetType();
FieldInfo[] fields = type.GetFields();
foreach (FieldInfo field in fields)
{
if (field.Name.Equals(p.ToString()))
{
object[] objs = field.GetCustomAttributes(typeof(PropertiesDesc), true);
if (objs != null && objs.Length > 0)
{
return ((PropertiesDesc)objs[0]).Desc;
}
else
{
return p.ToString() + "没有附加PropertiesDesc信息";
}
}
}
return "No Such field : " + p;
}
}

可以看到,这里不再需要手动判断哪个枚举值对应哪个字符串描述,而是通过获取枚举域的PropertiesDesc对象并返回其Desc属性。

当然,我们可以将上述代码改为通用的,将Properties改为Type,这样就可以处理所有的枚举。同时,在查找PropertiesDesc的位置增加一个缓存,根据Type和字段的Name进行缓存。优化后的代码如下:

public class PropertiesUtils
{
private static Dictionary<Type, Dictionary<string, string>> cache = new Dictionary<Type, Dictionary<string, string>>();

public static string GetDescByProperties(object p)
{
var type = p.GetType();
if (!cache.ContainsKey(type))
{
Cache(type);
}
var fieldNameToDesc = cache[type];
var fieldName = p.ToString();
return fieldNameToDesc.ContainsKey(fieldName) ? fieldNameToDesc[fieldName] : string.Format("Can not found such desc for field `{0}` in type `{1}`", fieldName, type.Name);
}

private static void Cache(Type type)
{
var dict = new Dictionary<string, string>();
cache.Add(type, dict);
var fields = type.GetFields();
foreach (var field in fields)
{
var objs = field.GetCustomAttributes(typeof(PropertiesDesc), true);
if (objs.Length > 0)
{
dict.Add(field.Name, ((PropertiesDesc)objs[0]).Desc);
}
}
}
}

3、泛型优化缓存器

由于上述代码已经实现了一定的通用性,我们将类名改为AttributeUtils。代码如下:

public class AttributeUtils
{
private static Dictionary<Type, Dictionary<string, string>> cache = new Dictionary<Type, Dictionary<string, string>>();

public static string GetDescByProperties<T>(object p)
{
var type = p.GetType();
if (!cache.ContainsKey(type))
{
Cache<T>(type);
}
var fieldNameToDesc = cache[type];
var fieldName = p.ToString();
return fieldNameToDesc.ContainsKey(fieldName) ? fieldNameToDesc[fieldName] : string.Format("Can not found such desc for field `{0}` in type `{1}`", fieldName, type.Name);
}

private static void Cache<T>(Type type)
{
var dict = new Dictionary<string, string>();
cache.Add(type, dict);
var fields = type.GetFields();
foreach (var field in fields)
{
var objs = field.GetCustomAttributes(typeof(T), true);
if (objs.Length > 0)
{
T obj = (T)objs[0];
Type attrType = obj.GetType();
PropertyInfo pi = attrType.GetProperty("Desc");
UnityEngine.Debug.Log(pi);

dict.Add(field.Name, pi.GetValue(obj, null).ToString());
}
}
}
}

使用这个通用工具来获取描述时需要注意,枚举上可以定义不同的Attribute来获取描述信息,但描述信息必须存储在名为Desc的属性中。如果您希望自定义属性名,可以增加一个参数来指定属性名,这部分代码留给有需要的读者自行实现。修改后,调用示例如下:

Debug.Log(AttributeUtils.GetDescByProperties<PropertiesDesc>(Properties.HP));
Debug.Log(AttributeUtils.GetDescByProperties<PropertiesDesc>(Properties.PhyAtk));

4、Attribute的其他应用场景

Attribute的应用场景非常广泛。例如,在进行ORM映射时,当您创建一个类,部分字段不想映射到数据库表中,部分字段需要映射,且有些字段的名称与数据库表中的字段名称不同,此时可以使用Attribute来标识哪些字段需要映射以及映射到数据库的哪个字段。

在网络框架开发中,有经验的开发者对使用Attribute进行自动消息派发应该比较熟悉。

总之,Attribute可以实现许多自动化任务,具体应用取决于您的需求和创意。

作者信息

洞悉

洞悉

共发布了 3994 篇文章