反射


反射

反射指程序可以訪問、檢測和修改它本身狀態或行為的一種能力。

程序集包含模塊,而模塊包含類型,類型又包含成員。反射則提供了封裝程序集、模塊和類型的對象。

您可以使用反射動態地創建類型的實例,將類型綁定到現有對象,或從現有對象中獲取類型。然后,可以調用類型的方法或訪問其字段和屬性。

優缺點

優點:

  • 1、反射提高了程序的靈活性和擴展性。
  • 2、降低耦合性,提高自適應能力。
  • 3、它允許程序創建和控制任何類的對象,無需提前硬編碼目標類。

缺點:

  • 1、性能問題:使用反射基本上是一種解釋操作,用於字段和方法接入時要遠慢於直接代碼。因此反射機制主要應用在對靈活性和拓展性要求很高的系統框架上,普通程序不建議使用。
  • 2、使用反射會模糊程序內部邏輯;程序員希望在源代碼中看到程序的邏輯,反射卻繞過了源代碼的技術,因而會帶來維護的問題,反射代碼比相應的直接代碼更復雜。

反射(Reflection)的用途

反射(Reflection)有下列用途:

  • 它允許在運行時查看特性(attribute)信息。
  • 它允許審查集合中的各種類型,以及實例化這些類型。
  • 它允許延遲綁定的方法和屬性(property)。
  • 它允許在運行時創建新類型,然后使用這些類型執行一些任務。

依賴反轉和依賴注入

依賴反轉原則(Dependency inversion principle,DIP)是指一種特定的解耦形式,使得高層次的類不依賴於低層次的類的實現細節,依賴關系被顛倒(反轉),從而使得低層次類依賴於高層次類的需求抽象。

該原則規定:

  1. 高層次的類不應該依賴於低層次的類,兩者都應該依賴於抽象接口。
  2. 抽象接口不應該依賴於具體實現。而具體實現則應該依賴於抽象接口。

在傳統的應用架構中,低層次的組件設計用於被高層次的組件使用,這一點提供了逐步的構建一個復雜系統的可能。在這種結構下,高層次的組件直接依賴於低層次的組件去實現一些任務。這種對於低層次組件的依賴限制了高層次組件被重用的可行性。

依賴反轉原則的目的是把高層次組件從對低層次組件的依賴中解耦出來,這樣使得重用不同層級的組件實現變得可能。

按照常理,軟件划分層也好,模塊也厚,通常都是將相同語義的元素放在一起,因此接口與其實現(類)應該處於一層或模塊之中。如下圖所示

這看似好像沒有問題,但是軟件的高層應用可能會發生變化,即來自客戶的需求會發生變化(這是常事兒啊)。當高層的應用發生了改變,那它依賴的低層對象所提供的服務也很可能要發生變化,因為高層要完成新的業務,低層要負責提供對應的服務。那么問題來了,誰來約定這個接口提供什么樣的服務?按照前面的邏輯,接口和實現放在低層中,那應該由低層提供,可是低層開發人員並不負責高層的應用邏輯。低層應該應該只關心自己那點事兒,即負責響應高層的需求,去按照需求提供實現服務。但現在接口放在低層維護,就應該由低層的開發人員負責體現需求的接口的“變更”(提供新的服務)。這樣會發現這樣的情況,即負責高層實現的開發人員,他們擁有需求,但無法定義描述需求的接口;負責低層的開發人員,他們不管需求,只應提供具體實現,卻要維護和應用需求有關的接口。這不就出現了很大的矛盾嗎?

因此,為了解決這樣的矛盾,人們提出將本應放在低層的接口放在高層,低層的實現依賴高層提供的接口,去實現相應的服務(請參見上圖所示)。本人認為這才是DIP中“倒置”的真正含義所在。

關於依賴注入,可以看下方鏈接
依賴注入的簡單理解 - nowthink - 博客園 (cnblogs.com)
.Net的依賴注入框架是using Microsoft.Extensions.DependencyInjection;

反射的第一個用處:依賴注入的事例代碼

using System;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;

namespace IspExample
{
    class Program
    {
        static void Main(string[] args)
        {
            //生成容器sp
            var sc = new ServiceCollection();
            sc.AddScoped(typeof(ITank),typeof(HeavyTank));//參數1:typeof(接口名),參數2:typeof(接口實現名),注冊服務,當實例化時就找到匹配這些接口和實現的實例注入到構造器中。
            sc.AddScoped(typeof(IVehicle), typeof(Car));
            sc.AddScoped<Driver>();//構造器Driver
            var sp = sc.BuildServiceProvider();//建立容器
            //---------分割線-----------
            //分割線以上是一次性的注冊,在程序啟動后注冊,
            //分割線以下,在程序的其他地方,只要你能看到sp的地方都可以用,不再有new操作符,從container里去調對象
            ITank tank = sp.GetService<ITank>();
            tank.Fire();///Boom!!!
            tank.Run();///Ka!! ka!! ka!!
            //當上面addscoped中后面的typeof中換成MediumTank,上面輸出的就是中型tank的方法。
            var driver = sp.GetService<Driver>();//找到符合IVehicle接口的實例car賦給driver,等同於var driver = new Driver(new Car());
            driver.Drive();//car is running...
        }
    }

    class Driver
    {
        private IVehicle _vehicle;
        public Driver(IVehicle vehicle)
        {
            _vehicle = vehicle;
        } 
        public void Drive() 
        {
            _vehicle.Run();
        }
    }
    interface IVehicle 
    { 
        void Run();
    } 
    interface IWeapon
    {
        void Fire();
    }
    class Car : IVehicle
    {
        public void Run() 
        {
            Console.WriteLine("Car is running...");
        }
    }
    class Truck : IVehicle
    {
        public void Run()
        {
            Console.WriteLine("Truck is running...");
        }
    }

    interface ITank:IVehicle,IWeapon
    {
    
    }
    class LightTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("Boom!");
        }
        public void Run()
        {
            Console.WriteLine("Ka ka ka");
        }
    }
    class MediumTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("Boom!!");
        }
        public void Run()
        {
            Console.WriteLine("Ka! ka! ka!");
        }
    }
    class HeavyTank : ITank
    {
        public void Fire()
        {
            Console.WriteLine("Boom!!!");
        }
        public void Run()
        {
            Console.WriteLine("Ka!! ka!! ka!!");
        }
    }
}

反射的第二個用處:更松的耦合,插件式開發編程

插件和主題程序不一起編譯,但一起工作

例子大致內容是,你是嬰兒車廠商,將嬰兒車的面板上按照數字順序顯示一排小動物頭像,然后可以選擇某個小動物,再點擊數字,就可以放出那個動物的叫聲多少次。

讓第三方程序,一直擴展小動物的叫聲,程序就可以一直進步,調用第三方插件就可以了。

主程序:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Loader;

namespace ReflectionExample
{
    class Program
    {
        static void Main(string[] args)
        {
            //找到Animals文件夾
            var folder =Path.Combine(Environment.CurrentDirectory,"Animals");
            var files = Directory.GetFiles(folder);
            var animalsTypes = new List<Type>();
            foreach (var file in files)
            {
                //加載文件夾中的所有dll
                var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
                var types = assembly.GetTypes();
                foreach (var t in types)
                {
                    if (t.GetMethod("Voice")!=null)
                    {
                        animalsTypes.Add(t);
                    }
                }
            }
            //以上是加載animals程序集並把方法為Voice的類型傳給animalsTypes
            //下面是調用方法
            while (true)
            {
                for (int i = 0; i < animalsTypes.Count; i++)
                {
                    Console.WriteLine($"{i+1}.{animalsTypes[i].Name}");
                }
                Console.WriteLine("================");
                Console.WriteLine("Please choose animal");
                int index = int.Parse(Console.ReadLine());
                if (index>animalsTypes.Count||index<1)
                {
                    Console.WriteLine("No such an animal.Try again!");
                    continue;
                }
                int times = int.Parse(Console.ReadLine());//動物叫的次數
                var t = animalsTypes[index - 1];//拿到動物類型
                var m = t.GetMethod("Voice");//拿到Voice方法
                var o = Activator.CreateInstance(t);//創建對象
                m.Invoke(o, new object[] {times});
                //MethodBase.Invoke(Object, Object[]):使用指定參數調用由當前實例表示的方法(Voice)或構造函數。
            }
        }
    }
}

再寫第三方程序:

using System;

namespace Animals.Lib
{
    public class cat
    {
        public void Voice(int times)
        {
            for (int i = 0; i < times; i++)
            {
                Console.WriteLine("Meow!");
            }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Text;

namespace Animals.Lib
{
    public class sheep
    {
        public void Voice(int times)
        {
            for (int i = 0; i < times; i++)
            {
                Console.WriteLine("Baa...");
            }
        }
    }
}

寫兩個舉個例子,生成解決方案后,進入程序所在文件夾的bin里找到.dll文件,粘貼到主程序文件夾的bin里的Animals文件夾里,再運行主程序,就成功了。
這種方式還是不方便,比如如果第三方程序把voice的字母拼錯,就沒法使用了,所以我們在主程序開發一個帶有接口(只能實現voice方法)的SDk供第三方開發插件,那么就方便了。

代碼如下:

using System;
using System.Collections.Generic;
using System.Text;

namespace BabyStroller.SDK
{
    public interface IAnimal
    {
        void Voice(int times);
    }
}

以及:

using System;

namespace BabyStroller.SDK
{
    public class UnfinishedAttribute:Attribute
    {
    //就是未完成的插件可以用這個特性進行標記,當sdk的提供商掃描到這個特性的時候就知道這個插件還沒有完成,就會忽略它的調用
    }
}

然后再在第一方程序內部引用sdk,第三方程序引用sdk的dll文件,並繼承IAnimal接口

如果在cat上加一個特性[Unfinished],如:

using BabyStroller.SDK;
using System;

namespace Animals.Lib
{
    [Unfinished]
    public class cat:IAnimal
    {
        public void Voice(int times)
        {
            for (int i = 0; i < times; i++)
            {
                Console.WriteLine("Meow!");
            }
        }
    }
}

再在第一方程序內做如下修改:

//將
if (t.GetMethod("Voice") != null)
     {
         animalsTypes.Add(t);
     }
//改成
if (t.GetInterfaces().Contains(typeof(IAnimal)))
   {
    var isUnfinished = t.GetCustomAttributes(false).Any(a=>a.GetType()== typeof(UnfinishedAttribute));
    if (isUnfinished) continue;
    animalsTypes.Add(t);
   }
//上述代碼是判斷第三方dll是否使用了IAnimal接口,以及特性是UnfinishedAttribute的就過濾掉

后面的調用方法也可以改成:

var m = t.GetMethod("Voice");//拿到Voice方法
var o = Activator.CreateInstance(t);//創建對象
m.Invoke(o, new object[] {times});//調用方法
// ==》改成:
var a = o as IAnimal;
a.Voice(times);


免責聲明!

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



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