程序集和反射(C#)

這裏我又嘮叨幾句,你們在學習的時候,如看書或者看視頻時以爲很是爽,由於感受基本都看得懂也都挺容易的,其實看懂是一回事,你本身會動手作出來是一回事,本身可以說出來又是另外一回事了。應該把學到的東西變成本身的東西,而不是依樣畫瓢。java

在說反射以前,咱們先來了解一下什麼是程序集?數據庫

程序集

程序集是.net中的概念,程序集能夠看做是給一堆相關類打一個包,至關於java中的jar包。小程序

程序集包含:緩存

  • 資源文件
  • 類型元數據(描述在代碼中定義的每一類型和成員,二進制形式)
  • IL代碼(這些都被封裝在exe或dll中)

exe與dll的區別。app

exe能夠運行,dll不能直接運行,由於exe中有一個main函數(入口函數)。框架

類型元數據這些信息能夠經過AssemblyInfo.cs文件來自定義。在每個.net項目中都存在一個AssemblyInfo.cs文件,代碼格式:ide

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// 有關程序集的常規信息經過如下
// 特性集控制。更改這些特性值可修改
// 與程序集關聯的信息。
[assembly: AssemblyTitle("ReflectedDemo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ReflectedDemo")]
[assembly: AssemblyCopyright("Copyright ©  2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// 將 ComVisible 設置爲 false 使此程序集中的類型
// 對 COM 組件不可見。  若是須要從 COM 訪問此程序集中的類型,
// 則將該類型上的 ComVisible 特性設置爲 true。
[assembly: ComVisible(false)]

// 若是此項目向 COM 公開,則下列 GUID 用於類型庫的 ID
[assembly: Guid("7674d229-9929-4ec8-b543-4d05c6500863")]

// 程序集的版本信息由下面四個值組成: 
//
//      主版本
//      次版本 
//      生成號
//      修訂號
//
// 能夠指定全部這些值,也可使用「生成號」和「修訂號」的默認值,
// 方法是按以下所示使用「*」: 
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

這些信息在哪裏體現呢?就在咱們程序集的屬性當中進行體現函數

咱們平時在安裝一些CS客戶端程序的時候,在安裝目錄下面會看見許多的程序集文件。性能

使用程序集的好處學習

  • 程序中只引用必須的程序集,減少程序的尺寸。
  • 程序集能夠封裝一些代碼,只提供必要的訪問接口。
  • 方便擴展。

如何添加程序集的引用?

直接添加程序集路徑或者添加解決方案中的項目引用。

當咱們須要擴展一個程序的時候,你可能會直接在原有的項目中進行添加,那這樣的話,若是你的這些代碼想共享給別人使用呢?你就能夠打包成一個程序集,而後別人只要經過引用你這個程序集就能夠進行擴展了。像咱們常見的.net第三方框架庫,如log4net、unity等等。

注意:不能添加循環引用

什麼是添加循環引用?就是說A項目若是添加了B項目的項目引用,那麼此時B項目不能再添加A項目的項目引用,也就是說添加項目引用時,必須是單向的,像咱們常見的三層框架之間的項目引用。

反射

關於反射,你只要是作.net開發,你就必定每天在用。由於VS的智能提示就是經過應用了反射技術來實現的,還有咱們經常使用的反編譯神器Reflector.exe,看它的名字就知道了。項目中比較常見的,是經過結合配置文件來動態實例化對象,如切換數據庫實例,或者Sprint.net的經過配置文件來實現依賴注入等。

反射技術其實就是動態獲取程序集的元數據的功能,反射經過動態加載dll,而後對其進行解析,從而建立對象,調用成員。

Type是對類的描述,Type類是實現反射的一個重要的類,經過它咱們能夠獲取類中的全部信息,包括方法、屬性等。能夠動態調用類的屬性、方法。

反射的出現讓建立對象的方式發生了改變,由於過去面完建立對象都是直接經過new。

dll裏面有兩部分東西:IL中間語言和metadate元素據。

在.NET中反射用到命名空間是System.Reflection,這裏我先經過一個Demo來看反射能作些什麼

一、  新建控制檯項目ReflectedDemo

二、  新建類庫項目My.Sqlserver.Dal

新建兩個類SqlServerHelper和SqlCmd,前者爲共有類,後者爲私有類

namespace My.Sqlserver.Dal
{
    public class SqlServerHelper
    {
        private int age = 16;
        public string Name { get; set; }
        public string Query()
        {
            return string.Empty;
        }
    }
   class SqlCmd
    {
    }
}

三、  項目ReflectedDemo,添加My.Sqlserver.Dal的項目引用,我這樣作的目的是爲了方便項目ReflectedDemo中的bin目錄中時刻存在My.Sqlserver.Dal.dll程序集。

using System;
using System.Reflection;

namespace ReflectedDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            //加載程序集文件,在bin目錄中查找
            Assembly assembly = Assembly.Load("My.Sqlserver.Dal");
            Console.WriteLine("----------------Modules----------------------");
            var modules = assembly.GetModules();
            foreach(var module in modules)
            {
                Console.WriteLine(module.Name);
            }
            Console.WriteLine("----------------Types----------------------");
            var types = assembly.GetTypes(); //獲取程序集中全部的類型,包括公開的和不公開的
            foreach(var type in types)
            {
                Console.WriteLine(type.Name);
                Console.WriteLine(type.FullName);
                var members= type.GetMembers(); //獲取Type中全部的公共成員
                Console.WriteLine("----------------members----------------------");
                foreach(var m in members)
                {
                    Console.WriteLine(m.Name);
                }
            }
            Console.WriteLine("----------------GetExportedTypes----------------------");
            var exportedTypes = assembly.GetExportedTypes(); //獲取程序集中全部的公共類型
            foreach(var t in exportedTypes)
            {
                Console.WriteLine(t.Name);
            }
           Console.WriteLine("----------------GetType----------------------");
           var typeName= assembly.GetType("SqlServerHelper");//獲取程序集中指定名稱的類型對象
           Console.WriteLine(typeName.Name);
        }
    }
}

動態建立對象

經過ass.CreateInstance(string typeName) 和Activator.CreateInstance(Type t)方法

他們之間的區別
ass.CreateInstance(string typeName) 會動態調用類的無參構造函數建立一個對象,返回值就是建立的對象,若是沒有無參構造函數就會報錯。

            Assembly assembly = Assembly.Load("My.Sqlserver.Dal");
            object obj = assembly.CreateInstance("My.Sqlserver.Dal.SqlServerHelper");
            Console.WriteLine(obj.GetType().ToString());

若是咱們來修改SqlServerHelper類的代碼,添加以下構造函數:

       public SqlServerHelper(int age)
        {
            this.age = age;
        }

這個時候再來運行建立實例的代碼就會報錯了,而編譯時是不報錯的。

因此咱們通常推薦使用Activator.CreateInstance方法來建立反射對象,由於此方法有許多重載,支持將參數傳遞給構造函數。

 

此時再調用就不會出現異常了。

Type類中有三個用得比較多的方法:

  • bool IsAssignableFrom(Type t):是否能夠從t賦值,判斷當前的類型變量是否是能夠接受t類型變量的賦值。
  • bool IsInstanceOfType(object o):判斷對象o是不是當前類的實例,當前類能夠是o的類、父類、接口
  • bool IsSubclassOf(Type t):判斷當前類是不是t的子類

Type類中還有一個IsAbstract屬性:判斷是否爲抽象的,包含接口。

它們經常使用的緣由是咱們經過反射能夠取到的東西太多了,咱們須要對數據進行過濾。

添加類BaseSql,讓類SqlServerHelper繼承自BaseSql

而後查看調用代碼:

            bool result = typeof(BaseSql).IsAssignableFrom(typeof(SqlServerHelper));
            Console.WriteLine(result);

            SqlServerHelper _SqlServerHelper = new SqlServerHelper(1);
            bool result = typeof(SqlServerHelper).IsInstanceOfType(_SqlServerHelper);
            Console.WriteLine(result);

            SqlServerHelper _SqlServerHelper = new SqlServerHelper(1);
            bool result = typeof(SqlServerHelper).IsSubclassOf(typeof(BaseSql));
            Console.WriteLine(result);

項目中經常使用的利用反射來動態切換數據庫Demo:

新建類庫項目My.Sql.IDal,並添加接口ISqlHelper。經過接口來實現數據庫操做的類的解耦,由於接口是抽象的。

    public interface ISqlHelper
    {
        string Query();
    }

添加類庫項目My.MySql.Dal,並新增類MySqlHelper.cs
My.Sqlserver.Dal、My.MySql.Dal項目分別添加對項目My.Sql.IDal的引用。讓SqlServerHelper繼承自接口ISqlHelper

    public class MySqlHelper : ISqlHelper
    {
        public string Query()
        {
             return this.GetType().ToString();
        }
    }
    public class SqlServerHelper :ISqlHelper
    {
        private int age = 16;
        public string Name { get; set; }
        public string Query()
        {
            return this.GetType().ToString();
        }
    }

添加App.config配置項

  <appSettings>
    <add key="DBName" value="My.Sqlserver.Dal,SqlServerHelper"/>
  </appSettings>

ReflectedDemo項目中Program.cs調用代碼:

            string str = ConfigurationManager.AppSettings["DBName"];
            string strAssembly = str.Split(',')[0];
            string strClass=str.Split(',')[1];
            Assembly assembly = Assembly.Load(strAssembly);
            Type t = assembly.GetType(strAssembly + "." + strClass);
            ISqlHelper obj = Activator.CreateInstance(t) as ISqlHelper;
            Console.WriteLine(obj.Query());

這樣每次須要切換數據庫時,只要修改配置文件就能夠了。

項目結構:

注意:反射雖然很強大,但倒是比較耗性能的,因此通常和緩存結合起來使用。

項目源碼:ReflectedDemo.zip

相關文章
相關標籤/搜索