反射簡介—C#特性和反射


.NET編譯器的任務之一就是為所有定義和引用的類型生成元數據描述。除了程序集中標准的元數據外,.NET平台還支持特定(attribute)把更多的元數據嵌入到程序集中。

.NET特性擴展了抽象的System.Attribute基類,.NET中有很多預定義的特性,例如:[DllImport]、[Obsolete]和[Serializable]等等。

看一個Obsolete使用的例子,Obsolete特性用來標記不用的類或成員,當其他程序試圖訪問該項的時候,將會得到一個警告或錯誤描述。

static class StringUtil
{
    public static string ReverseString(string str)
    {
        Console.WriteLine("call reverseString");
        return "";
    }

    [Obsolete("this legacy method should not be used", true)]
    public static string LegacyReverseString(string str)
    {
        Console.WriteLine("call legacyReverseString");
        return "";
    }
}

class Program
{
    static void Main(string[] args)
    {
        string str = StringUtil.LegacyReverseString("Hello World!");
        Console.Read();
    }
}

使用上面的代碼,我們就會的到一個錯誤消息,提示我們不應該在使用這個方法了

當然,我們也可以通過代碼來添加自定義特性,在開始自定義特性之前,我們需要知道以下概念。

自定義特性

在代碼中,我們可以創建自定義的特性類型,但是這個類型一定要直接或間接從System.Attribute派生。下面我們就定義了一個TableAttribute特性:

[AttributeUsage(AttributeTargets.Class, Inherited=false, AllowMultiple=false)]
public class TableAttribute : Attribute
{
    public TableAttribute()
    {
    }

    public TableAttribute(string tableName)
    {
        this.TableName = tableName;
    }

    public string TableName { get; set; }
}

注意:對一個特性類名使用Attribute后綴是一個慣例。然而,當我們把特性添加到一個程序實體,可以選擇省略Atrribute后綴。編譯器會首先在System.Attribute的派生類中查找被添加的特性類。如果沒有找到,那么編譯器會添加 Attribute后綴繼續查找。

例如:當我們在代碼中使用的時候,特性表現為[Obsolete],但是實際上類型是ObsoleteAttribute,而不是代碼中的Obsolete。當.NET Framework進行名稱轉換的時候,所有的.NET特性(包括自定義特性)都將加上一個Attribute標記的后綴。

AttributeUsage

在上面的自定義特性中,有下面一行代碼,我們給自定義特性應用了一個AttributeUsage特性。

[AttributeUsage(AttributeTargets.Class, Inherited=false, AllowMultiple=false)]

AttributeUsage類是一個預定義特性類,通過這個特性,我們可以控制自定義特性的使用,它描述了一個定制特性如和被使用。

[Serializable]
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
[ComVisible(true)]
public sealed class AttributeUsageAttribute : Attribute
{
    public AttributeUsageAttribute(AttributeTargets validOn);

    public bool AllowMultiple { get; set; }
   
    public bool Inherited { get; set; }
    
    public AttributeTargets ValidOn { get; }
}

通過代碼可以看到,AttributeUsage有三個屬性:

  • ValidOn

    從代碼中可以看到ValidOn的類型為System.AttributeTargets, 而AttributeTargets本身是

    一個枚舉,這樣就可以通過按位"或"運算組合 AttributeTargets,從而指示哪些程序元素是有效的。

    例如:

    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]

  • AllowMultiple

    這個屬性標記了特性能否被重復放置在同一個程序實體前多次。

  • Inherited

    這個屬性來控制定制特性的繼承規則,表示特性能否被繼承。

從上面的介紹可以看到,創建自定義特性的大概步驟:

  1. 聲明特性類,由 System.Attribute 直接或間接派生
  2. 使用AttributeUsage特性來控制自定義特性
  3. 聲明特性類構造函數

特性和反射結合

當我們使用特性的時候,只是給程序集添加了一些元數據。當結合反射使用的時候,特性就能發揮很大的作用了。

下面看一個特性和反射結合的例子,在例子中自定義了Table和Column特性,然后把這些特性應用到了我們的User類型上面;然后結合一個自定義的ORM類型,將對象的Inster()操作轉換成SQL語句。

namespace AttributeTest
{
    [AttributeUsage(AttributeTargets.Class, Inherited=false, AllowMultiple=false)]
    public class TableAttribute : Attribute
    {
        public TableAttribute()
        {
        }

        public TableAttribute(string tableName)
        {
            this.TableName = tableName;
        }

        public string TableName { get; set; }
    }

    [AttributeUsage(AttributeTargets.Property, Inherited=false, AllowMultiple=false)]
    public class ColumnAttribute:Attribute
    {
        public ColumnAttribute()
        {
        }

        public ColumnAttribute(string columnName)
        {
            this.ColumnName = columnName;
        }

        public ColumnAttribute(string colunmName, DbType dbType)
        {
            this.ColumnName = colunmName;
            this.DbType = dbType;
        }

        public string ColumnName { get; set; }
        public DbType DbType { get; set; }
    }


    public class CustomizeORM
    {
        public void Insert(object table)
        {
            Type type = table.GetType();
            Dictionary<string, string> columnValueDict = new Dictionary<string, string>();
            StringBuilder SqlStr = new StringBuilder();
            SqlStr.Append("insert into ");
            TableAttribute tableAttribute = (TableAttribute)type.GetCustomAttributes(typeof(TableAttribute), false).First();
            SqlStr.Append(tableAttribute.TableName);
            SqlStr.Append("(");

            foreach (var prop in type.GetProperties())
            {
                foreach (var attr in prop.GetCustomAttributes())
                {
                    string value = prop.GetValue(table).ToString();
                    ColumnAttribute columnAttribute = attr as ColumnAttribute;
                    if (columnAttribute != null)
                    {
                        columnValueDict.Add(columnAttribute.ColumnName, value);
                    }
                }
            }

            foreach (var item in columnValueDict)
            {
                SqlStr.Append(item.Key);
                SqlStr.Append(",");

            }
            SqlStr.Remove(SqlStr.Length - 1, 1);
            SqlStr.Append(") values('");
            foreach (var item in columnValueDict)
            {
                SqlStr.Append(item.Value);
                SqlStr.Append("','");
            }
            SqlStr.Remove(SqlStr.Length - 2, 2);
            SqlStr.Append(")");
            Console.WriteLine(SqlStr.ToString());
        }
    }

    [Table("Users")]
    public class User
    {
        [Column(ColumnName="Id",DbType=DbType.Int32)]
        public int UserID{get;set;}
        [Column(ColumnName="Name",DbType=DbType.String)]
        public string UserName { get; set; }
        [Column(ColumnName = "Age", DbType = DbType.Int32)]
        public int Age { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            CustomizeORM customizeORM = new CustomizeORM();
            User user = new User() { UserID = 1, UserName = "Wilber", Age = 28};
            customizeORM.Insert(user);

            Console.Read();
        }
    }
     
}

代碼的執行結果為:

從這個例子中可以看到,通過特性給程序集加入的元數據,可以在運行時被反射程序得到並使用。

通過IL代碼可以看到特性的簡化,代碼中我們使用Table、Column對User類型進行修飾,在IL代碼中都加上了Attribute后綴;同時還可以看到,這些特性都變成了User類型的元數據。

總結

通過本文介紹了.NET特性,同時介紹了自定義特性需要的基本知識。

特性本身只是給程序集添加一些元數據,當結合反射使用的時候,這些被添加的元數據才能發揮更大的作用。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM