1. 程式人生 > >Unity遊戲開發——C#特性Attribute與自動化

Unity遊戲開發——C#特性Attribute與自動化

這篇文章主要講一下C#裡面Attribute的使用方法及其可能的應用場景。

比如你把玩家的血量、攻擊、防禦等屬性寫到列舉裡面。然後介面可能有很多地方要根據這個列舉獲取屬性的描述文字。

比如你做網路框架的時候,一個協議號對應一個類的處理或者一個方法。

比如你做ORM,一個類的屬性是否對映持久化檔案中的屬性,對映過去的屬性名是什麼。

1、什麼是Attribute

如果用過Java的Annotation的同學,可以把Attribute當成Annotation來看待。還不瞭解Attribute的同學不用急,我們看一下官方的解釋:

The 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.

All attribute types derive directly or indirectly from the 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. Use the  class to specify the target element to which the attribute is applied.

The Attribute class provides convenient methods to retrieve and test custom 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能應用到任何target元素;多個Attribute能應用到相同的元素;如果你看了官方的解釋不明白,看了我的翻譯也不明白。也沒事。。。我們接下來舉個例子看看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; }

}
沒錯,看起來是不是覺得很簡單。

然後我們就可以把上面定義的PropertiesDesc加到Properties上面,像這樣:
public enum Properties
{

    [PropertiesDesc("血量")]
    HP = 1,

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

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

    [PropertiesDesc("法術攻擊")]
    MagAtk = 4,

    [PropertiesDesc("法術防禦")]
    MagDef = 5
}
OK。這樣,我們相當於就把一個文字描述資訊通過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、還能幹什麼?

    Attribute能幹的事情太多了,比如你寫了個類,想做ORM對映,裡面有些欄位不想對映到表,有些想對映到表。有些欄位可能名字和表的欄位不一樣。這時候你就可以通過Attribute來標識哪個欄位需要被對映,對映到資料庫哪個欄位。等等。

    做過網路框架的同學也應該比較熟悉的一個應用,使用Attribute來做自動的訊息派發。

    總之,Attribute可以做很多自動化的事情,就看你怎麼用了。