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("--------------------------------------------------------------------");
            }
        }
    }
}