1. 程式人生 > >6.C#知識點:反射

6.C#知識點:反射

結束 來看 struct blog ins body methods 幫我 deb

1.反射是什麽?

  反射提供描述組件,模塊和類型的對象(類型為Type)。您可以使用反射來動態創建類型的實例,將類型綁定到現有對象,或從現有對象獲取類型,並調用其方法或訪問其字段和屬性。如果您在代碼中使用屬性,反射使您可以訪問它們。有關更多信息,請參閱屬性。-----來自微軟官方。

  微軟的解釋我覺得還可以。用大白話講就是我們可以以通過反射讓我們知道位置類型的信息。類似顯示生活中的B超啊。醫生用B超看到孕婦肚子裏的內部情況,因為醫生無法從內部查看。反射也是一樣,對於位置類型。或者引用過來的dll。我們是不知道內部情況的。但是可以通過反射。蝙蝠的超聲波也是。通過聲波反射回來,得知前方是否有障礙。這就是反射的功能。如果要問反射內部是如何實現的。不好意思。目前我也不知道。哈哈哈哈。

  簡單的來說,我們的程序是有dll的組成的,dll裏面有許許多多的類組成。類裏面又有字段,屬性和方法。反射的作用就是給個dll就能知道有哪些類,通過類又能知道有哪些成員。那麽.net裏面的反射是怎麽做到呢?那下面就要介紹幾個種類的反射類了。

(1)使用Assembly定義和加載程序集,加載在程序集清單中列出模塊,以及從此程序集中查找類型並創建該類型的實例。
(2)使用Module了解包含模塊的程序集以及模塊中的類等,還可以獲取在模塊上定義的所有全局方法或其他特定的非全局方法。
(3)使用ConstructorInfo了解構造函數的名稱、參數、訪問修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等。


(4)使用MethodInfo了解方法的名稱、返回類型、參數、訪問修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等。
(5)使用FiedInfo了解字段的名稱、訪問修飾符(如public或private)和實現詳細信息(如static)等,並獲取或設置字段值。
(6)使用EventInfo了解事件的名稱、事件處理程序數據類型、自定義屬性、聲明類型和反射類型等,添加或移除事件處理程序。
(7)使用PropertyInfo了解屬性的名稱、數據類型、聲明類型、反射類型和只讀或可寫狀態等,獲取或設置屬性值。
(8)使用ParameterInfo了解參數的名稱、數據類型、是輸入參數還是輸出參數,以及參數在方法簽名中的位置等。這段話是從大牛的博客拷貝------->傳送門

  

下面我依次重點的介紹幾個詳細的類。

首先是Assembly。這個存在於System.Reflection命名空間下面。我主要講它的3個加載程序集的的方法和區別。Load,LoadForm,LoadFile。

講了這門多文字,先從代碼看看語法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace 反射Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            //加載程序集
            Assembly assembly = Assembly.Load("TestDLL");

            //輸出程序集的強名稱
            Console.WriteLine(assembly.FullName);
            Console.ReadKey();
        }
    }
}

技術分享圖片

Load方法就是通過程序集的的名稱加載程序,但是需要要加載的程序集在當前程序集的bin目錄下才能找得到。

LoadForm

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace 反射Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            #region Load方法
                    //加載程序集
                   // Assembly assembly = Assembly.Load("TestDLL");

                    //輸出程序集的強名稱
                    //Console.WriteLine(assembly.FullName);
                    //Console.ReadKey();
            #endregion

            Assembly assmbly1 = Assembly.LoadFrom(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll");
            Console.WriteLine(assmbly1.FullName);
            Console.ReadKey();
        }
    }
}

LoadForm是通過路徑進行創建。返回加載的程序集。

來看最後一個loadFile

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace 反射Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            #region Load方法
            //加載程序集
            // Assembly assembly = Assembly.Load("TestDLL");

            //輸出程序集的強名稱
            //Console.WriteLine(assembly.FullName);
            //Console.ReadKey();
            #endregion

            #region LoadFrom方法
            //Assembly assmbly1 = Assembly.LoadFrom(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll");
            //Console.WriteLine(assmbly1.FullName);
            ///Console.ReadKey();
            #endregion

            #region LoadFile方法
            Assembly assmbly2 = Assembly.LoadFile(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll");
            Console.WriteLine(assmbly2.FullName);
            Console.ReadKey();
            #endregion
        }
    }
}

這個三個方法語法都很簡單。現在來說說這個三個的區別,和優缺點。

LoadFrom和Load:LoadForm同一個程序集只加載一次,就算加載了不通的路徑相同的程序集,他也只能給你返回之前的程序集,LoadFrom只能用於加載不同標識的程序集, 也就是唯一的程序集, 不能用於加載標識相同但路徑不同的程序集。

LoadFile和LoadForm:loadFile只會加載本身程序集,不會加在其引用的程序集,LoadForm和Load是會加載的其引用程序集。而且LoadFile同一個程序集只能加載一次。這個和LoadForm是一樣。

從性能上看 LoadForm沒有Load好,功能上也是load強一點。應用的時候如果loadFom和load都滿足需求,建議用load。

這幾個方法還幾個重載版本。由於本片只是基礎篇。篇幅不宜過多。想深入了解的小夥伴可以查詢MSN看看文檔。最詳細的說明就是文檔。但是文檔說的比較官方。結合大牛們寫的博客。更容易懂一點。

好了我們開始下一個階段了。程序集現在我們得了。我開始看看程序集裏面有些啥?

技術分享圖片

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace 反射Demo
{
    class Program
    {
        static void Main(string[] args)
        {


            #region LoadFrom方法
            Assembly assmbly = Assembly.LoadFrom(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll");
            Type[] types = assmbly.GetTypes();
            foreach (var item in types)
            {
                Console.WriteLine("類:"+item.Name);

            }
            Console.ReadKey();
            #endregion

        }
    }
}

技術分享圖片

assmbly.GetTypes()這個方法或獲取了所有類。
下面我我展示下獲取字段和方法的字段
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace 反射Demo
{
    class Program
    {
        static void Main(string[] args)
        {


            #region LoadFrom方法
            Assembly assmbly = Assembly.LoadFrom(@"C:\Users\DH\Documents\visual studio 2017\Projects\反射Demo\TestDLL\bin\Debug\TestDLL.dll");
            Type[] types = assmbly.GetTypes();
            foreach (var classitem in types)
            {
                Console.WriteLine("類:"+ classitem.Name);
                foreach (var fileditem in classitem.GetFields())
                {
                    Console.WriteLine("字段名:"+ fileditem.Name);
                }
                foreach (var methodItem in classitem.GetMethods())
                {
                    Console.WriteLine("方法名:"+methodItem.Name);
                }

            }
            Console.ReadKey();
            #endregion

        }
    }
}

技術分享圖片

可以看的出來我們將字段和方法名都給反射出來了,但是有個問題就是我們父類的方法也給弄出來了。我們可以修改下。這個地方有個重載版本。GetMethods有個重載方法可以通過BindingFlags枚舉參數進行篩選父類的方法,或者其他的。 BindingFlags這個枚舉類型。這裏就不多講。未來我會慢慢補全。

簡單的就是這麽多了。反射能做好多事情,非常靈活。我們抽象工廠裏面就會用到反射。我們的工廠。就是通過反射創建出來。下面我寫個demo演示下其作用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TestDLL;

namespace 反射Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Assembly assmbly = Assembly.Load("TestDLL");
            SqlServerHelper helper =(SqlServerHelper)assmbly.CreateInstance("TestDLL.SqlServerHelper");
            helper.Query();
            Console.ReadKey();
        }
    }
}

小夥伴們可以發現。我們實例化了一個SqlServerHelper對象,但是我們沒有用正常的new方法,而是用了反射。這個時候有的小夥伴可能就會說這是脫褲子放屁,直接new多簡單。在這裏是沒錯,但是放在真正的項目裏直接new是當時爽,需求變動就等著哭吧,比如說以後領導對你說,公司的數據庫不用SqlServer了,換成Oracle了。這是時候如果你是new的話還要改這裏的代碼,實際情況可能更復雜。但是用了我們的反射,這種煩惱就不會存在了。

CreateInstance("TestDLL.SqlServerHelper") 這個方法參數我們完全可以通過配置文件修改。這個類就不需要變了。我來演示下代碼

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestDLL
{
    public interface IDBHelper
    {
        void Query();
    }
}

先創建一個約定數據操作的接口 IDBHlper類。規定有一個Query方法,然後讓SqlServerHelper繼承這個接口,並且實現這個方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestDLL
{
    public class SqlServerHelper: IDBHelper
    {
        public void Query()
        {
            Console.WriteLine("這是測試");
        }
    }
}

然後修改mian函數的方法

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <appSettings>
    <add key="Helper" value="TestDLL.SqlServerHelper"/>
  </appSettings>
</configuration>
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TestDLL;

namespace 反射Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Assembly assmbly = Assembly.Load("TestDLL");
            string helperkey = ConfigurationManager.AppSettings["Helper"];
            IDBHelper helper =(IDBHelper)assmbly.CreateInstance(helperkey);
            helper.Query();
            Console.ReadKey();
        }
    }
}

這時候如果要將mian函數裏面helper切換成Oracle的只要添加一個繼承於IDBheper接口的類,然後實現方法,在修改配置文件指向這個類,然後就可以了。對於main函數我們是一點不需要動的,這是就是我們所有的高內聚低耦合。完成解耦。易於修改。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestDLL
{
    public class OracleHelper : IDBHelper
    {
        public void Query()
        {
            Console.WriteLine("我是Orcle數據庫");
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <appSettings>
    <add key="Helper" value="TestDLL.OracleHelper"/>
  </appSettings>
</configuration>
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TestDLL;

namespace 反射Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            Assembly assmbly = Assembly.Load("TestDLL");
            string helperkey = ConfigurationManager.AppSettings["Helper"];
            IDBHelper helper =(IDBHelper)assmbly.CreateInstance(helperkey);
            helper.Query();
            Console.ReadKey();
        }
    }
}

mian函數一點都沒有變化。運行查看結果

技術分享圖片

到了這個裏,我已經演示了一個反射應用場景了。其實VS本身就很多地方用了反射。比如。創建對象調用方法時候技術分享圖片VS直接只能幫我們列出這個對象下的成員。這個就是通過反射。其實還有很多。等待大家去發現。反射應該屬於C#裏面的高級知識點了。目前所說的只是冰山一角。

Ok。講到這裏就結束了哈。

如果剛開始學習的小夥伴還有疑問的話,可以評論咱們一起學習。

如果哪位大牛隨便瞄到個錯誤,也請告之我,讓我能夠進步。

6.C#知識點:反射