C Sharp/特性與反射

維基教科書,自由的教學讀本

反射[編輯]

反射是.NET Framework 的功能,並不是C#語言特有的功能。可理解為:給一個對象,在不用new操作符的情況下,也不知道靜態類型的情況下,創建一個同類型的對象還能訪問這個對象的各個成員。單元測試、依賴注入、泛型編程都是基於反射機制。一般情況下,使用反射時並不能感覺到,因為大多數使用的都是封裝好的反射。

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

namespace eg1Part1
{
        class Program
        {
            static void Main(string[] args)
            {
            ITank tank = new MediumTank();
            //=================分割线===========================
            //分割线以下不再用静态类型,而是动态的获取类信息并实例化
            var t = tank.GetType();
            object o = Activator.CreateInstance(t);
            MethodInfo fireMi = t.GetMethod("Fire");
            MethodInfo runMi = t.GetMethod("Run");
            fireMi.Invoke(o,null);
            runMi.Invoke(o,null);
            }
        }

        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("[Light]Boom!");
            }

            public void Run()
            {
                Console.WriteLine("[Light]KA!");
            }
        }

        class MediumTank : ITank
        {
            public void Fire()
            {
                Console.WriteLine("[Medium]Boom!Boom!");
            }

            public void Run()
            {
                Console.WriteLine("[Medium]KA!KA!");
            }
        }

        class HeavyTank : ITank
        {
            public void Fire()
            {
                Console.WriteLine("[Heavy]Boom!Boom!Boom!");
            }

            public void Run()
            {
                Console.WriteLine("[Heavy]KA!KA!KA!");
            }
        }
}

使用反射需要引用命名空間【using System.reflection】,這個命名空間包含如下的類:

  • 使用【Assembly】定義和加載程序集,加載在程序集清單中列出模塊,以及從此程序集中查找類型並創建該類型的實例
  • 使用【Module】了解包含模塊的程序集以及模塊中的類等,還可以獲取在模塊上定義的所有全局方法或其他特定的非全局方法
  • 使用【MemberInfo】
  • 使用【ConstructorInfo】了解構造函數的名稱、參數、訪問修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等
  • 使用【MethodInfo】了解方法的名稱、返回類型、參數、訪問修飾符(如pulic 或private)和實現詳細信息(如abstract或virtual)等
  • 使用【FiedInfo】了解字段的名稱、訪問修飾符(如public或private)和實現詳細信息(如static)等,並獲取或設置字段值
  • 使用【EventInfo】了解事件的名稱、事件處理程序數據類型、自定義屬性、聲明類型和反射類型等,添加或移除事件處理程序
  • 使用【PropertyInfo】了解屬性的名稱、數據類型、聲明類型、反射類型和只讀或可寫狀態等,獲取或設置屬性值
  • 使用【ParameterInfo】了解參數的名稱、數據類型、是輸入參數還是輸出參數,以及參數在方法簽名中的位置等
  • System.Type

獲取反射類型的三種方式:

  • 通過Typeof獲取某個值的類型 System.Type type = typeof(myClassName);
  • 通過GetType獲取對象類型 System.Type type1 = myInstance.GetType();
  • 通過GetType獲取類名稱類型 System.Type type = System.Type.GetType("myNameSpace.myClassName");

使用System.Activator.CreateInstance方法創建指定類型的實例,並可為實例的數據成員賦值。

依賴注入[編輯]

依賴反轉原則(DIP)是一個概念/設計原則。依賴注入(DI)是在依賴反轉概念基礎上,結合接口、反射機制形成的應用。

把各種類、接口放到容器中,就是「注入」。注入時還可以設置是每次創建一個新對象,還是創建一個單例模式。需要服務的實例,向容器要實例即可。

服務(功能)容器也稱為Service Provider。

一般情況下,主體程序都會發布包含程序開發接口(API)的程序開發包(SDK),使用SDK中的API開發插件就會比較容易、高效。API里不一定都是接口,也有可能是一組方法,一組類,一組接口。依賴注入的高自由度意味着錯誤率提高,例如調方法時大小寫寫錯,就無法找到對應的方法再成功調用了。開發插件過程中,為了避免自由度過高導致的錯誤,需要有一定的約束,就是SDK中的API。

首先需要用Manage Nuget增加並引用命名空間Microsoft.Extensions.DependencyInjection。然後:

                  //#####基础用法#####
            var sc = new ServiceCollection();
            sc.AddScoped(typeof(ITank),typeof(HeavyTank)); //括号中参数的含义:接口,类实现了此接口的类;Typeof代表动态的拿到这个类的信息
            var sp = sc.BuildServiceProvider();
            //以上为注册(把东西放进容器),ServiceCollection就是Container
            //以下是从容器中“要对象”(只要能够看到Service Provider的地方都可以这么用)
            ITank tank = sp.GetService<ITank>();
            tank.Fire();
            tank.Run();

更複雜的功能:

            //#####进阶用法#####
            var sc = new ServiceCollection();
            sc.AddScoped(typeof(ITank), typeof(HeavyTank));
            sc.AddScoped(typeof(IVehicle), typeof(Truck));
            sc.AddScoped<Driver>();//Driver的构造函数参数自动从容器包含的实例中填入
            var sp = sc.BuildServiceProvider();
            //==================分割线===================================
            var driver = sp.GetService<Driver>();
            driver.Drive();

加載插件的例子[編輯]

using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Loader;

namespace BabyStroller.app
{
    class Program
    {
        static void Main(string[] args)
        {
            //Console.WriteLine(Environment.CurrentDirectory);  
            //先输出这个路径,找到它,在此处新建一个Animal文件夹用来放插件(dll文件)
            var folder = Path.Combine(Environment.CurrentDirectory, "Animals");
            var files = Directory.GetFiles(folder);
            var animalTypes = new List<Type>();
            foreach (var file in files)//例如,该文件夹下有2个dll文件,每个含2个classes,每个class有一个Voice方法
            {
                var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
                var types = assembly.GetTypes();
                foreach (var t in types)
                {
                    if (t.GetMethod("Voice") != null)
                    {
                        animalTypes.Add(t);
                    }
                }
            }

            while (true)
            {
                for (int i = 0; i < animalTypes.Count; i++)
                {
                    Console.WriteLine($"{i+1}.{animalTypes[i].Name}");
                }

                Console.WriteLine("========================================");
                Console.WriteLine("Please input an animal's sequence number:");

                int index = int.Parse(Console.ReadLine());
                if (index>animalTypes.Count || index<1)
                {
                    Console.WriteLine("No such an animal,Please try again");
                    continue;
                }

                Console.WriteLine("How many times?");
                int times = int.Parse(Console.ReadLine());
                var t = animalTypes[index - 1];
                var m = t.GetMethod("Voice");
                var o = Activator.CreateInstance(t);
                m.Invoke(o, new object[] { times });
                Console.WriteLine("--------------------------------------------------------------------");
                Console.WriteLine("--------------------------------------------------------------------");

            }
        }
    }
}

更好的辦法是,主體軟件的sdk代碼中有IAnimal接口和Unfinished類(派生於Attribute)。主體軟件直接引用該sdk。每個服務的類都要實現IAnimal接口且沒有被[Unfinished]修飾

using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Loader;
using System.Linq;
using BabyStroller.sdk;

namespace BabyStroller.app
{
    class Program
    {
        static void Main(string[] args)
        {
            //Console.WriteLine(Environment.CurrentDirectory);  
            //先可以先输出这个路径,找到它然后建一个Animal文件夹
            var folder = Path.Combine(Environment.CurrentDirectory, "Animals");
            var files = Directory.GetFiles(folder);
            var animalTypes = new List<Type>();
            foreach (var file in files)
            {
                var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(file);
                var types = assembly.GetTypes();
                foreach (var t in types)
                {
                    //有了SDK就不用以下的操作了
                    //if (t.GetMethod("Voice") != null)
                    //{
                    //    animalTypes.Add(t);
                    //}
                    if (t.GetInterfaces().Contains(typeof(IAnimal))) ;
                    {
                        var isUnfinished = t.GetCustomAttributes(false).Any(a => a.GetType() == typeof(UnfinishedAttribute));
                        if (isUnfinished)
                        {
                            continue;
                        }
                        animalTypes.Add(t);
                    }
                }
            }

            while (true)
            {
                for (int i = 0; i < animalTypes.Count; i++)
                {
                    Console.WriteLine($"{i+1}.{animalTypes[i].Name}");
                }

                Console.WriteLine("========================================");
                Console.WriteLine("Please input an animal's sequence number:");

                int index = int.Parse(Console.ReadLine());
                if (index>animalTypes.Count || index<1)
                {
                    Console.WriteLine("No such an animal,Please try again");
                    continue;
                }

                Console.WriteLine("How many times?");
                int times = int.Parse(Console.ReadLine());
                var t = animalTypes[index - 1];
                var m = t.GetMethod("Voice");
                var o = Activator.CreateInstance(t);
                //m.Invoke(o, new object[] { times });
                //改为下边的写法
                var a = o as IAnimal;
                a.Voice(times);
                Console.WriteLine("--------------------------------------------------------------------");
                Console.WriteLine("--------------------------------------------------------------------");
            }
        }
    }
}