C#反射机制介绍

先看下面一个动物点名系统的简单例子:

有一个Animal的抽象动物父类,里面定义了Name、Age两个属性和一个Shout()方法,Animal类定义如下:

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

namespace Animal
{
    /// <summary>
    /// 抽象父类
    /// </summary>
    public abstract class Animal
    {
        /// <summary>
        /// Name属性
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Age属性
        /// </summary>
        public int Age { get; set; }

        /// <summary>
        /// Shout抽象方法
        /// </summary>
        public abstract void Shout();
    }
}

分别定义Cat、Dog类继承自Animal类,Cat类定义如下:

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

namespace Animal
{
    public class Cat :Animal
    {
        /// <summary>
        /// 构造函数初始化
        /// </summary>
        public Cat()
        {
            base.Name = "汤姆";
            base.Age = 2;
        }

        public override void Shout()
        {
            Console.WriteLine("喵喵喵,我是{0},今年{1}岁",
                base.Name,base.Age);
        }
    }
}

Dog类定义如下:

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

namespace Animal
{
    public class Dog : Animal
    {
        /// <summary>
        /// 构造函数初始化
        /// </summary>
        public Dog()
        {
            base.Name = "布鲁斯";
            base.Age = 3;
        }

        public override void Shout()
        {
            Console.WriteLine("汪汪汪,我是{0},今年{1}岁",
                base.Name, base.Age);
        }
    }
}

应用场景:在一个控制台程序中,输入具体的动物的类型,根据输入的动物类型,输出Name、Age和Shout()方法,使用传统方式实现的代码如下:

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

namespace ReflectionCon
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请录入动物类型:");
            string type = Console.ReadLine().Trim();

            Animal.Animal a = null;
            switch (type)
            {
                case "cat":
                    a = new Cat();
                    a.Shout();
                    break;
                case "dog":
                    a = new Cat();
                    a.Shout();
                    break;
            }
            Console.ReadKey();
        }
    }
}

程序运行结果如下:

那么问题来了:如果我们想要增加一个动物类型,那么就需要修改现有的代码,在switch里面增加判断。但是这种方式很不利于以后的维护,违反了开闭原则,每次增加一个动物类型的时候,都需要修改代码。那么有没有其他方式可以做到不用修改代码就可以实现呢?答案是肯定的,那就是使用我们接下来要讲的反射,先来了解一下什么是反射。

一、什么是反射

在讲解什么是反射之前,先来了解应用程序的结构。

程序代码在编译后生成可执行的应用,我们首先要了解这种可执行应用程序的结构。

应用程序结果分为应用程序域-程序集-模块-类型-成员几个层次,公共语言运行时(CLR)加载器管理应用程序域,这种管理包括将每个程序集加载到相应的应用程序域以及控制每个程序集中类型层次结构的内存布局。

程序集包含模块,而模块包含类型,类型又包含成员,反射则提供了封装程序集、模块和类型的对象。我们可以使用反射动态地创建类型的实例,将类型绑定到现有对象或从现有对象中获取类型,然后调用类型的方法或访问其字段和属性。

那么究竟什么是反射呢?

反射(Reflection)是.NET中的重要机制,可以在运行时获得.NET中每一个类型(包括类、结构、委托、接口和枚举等)的成员,包括方法、属性、事件、以及构造函数等。还可以获得每个成员的名称、限定符和参数等。有了反射,即可对每一个类型了如指掌。如果获得了构造函数的信息,即可直接创建对象,即使这个对象的类型在编译时还不知道。

二、反射的用途

1、使用Assembly定义和加载程序集,加载在程序集清单中列出模块,以及从此程序集中查找类型并创建该类型的实例。

2、使用Module了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。

3、使用ConstructorInfo了解构造函数的名称、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors()或GetConstructor()方法来调用特定的构造函数。

4、使用MethodInfo了解方法的名称,返回类型、参数、访问修饰符(如public或private)和实现详细信息(如abstract或virtual)等。使用Type的GetMethods()或GetMethod()方法来调用特定的方法。

5、使用FiledInfo了解字段的名称、访问修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。

6、使用EventInfo了解事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等,添加或移除事件处理程序。

7、使用PropertyInfo了解属性的名称、数据类型、声明类型、反射类型和只读或可写状态等,获取或设置属性值。

8、使用ParameterInfo了解参数的名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等。

三、反射用到的命名空间及主要类

1、命名空间

System.Reflection
System.Type
System.Reflection.Assembly

2、反射用到的主要类

Type类:该类位于System.Type命名空间下面,通过这个类可以访问任何给定数据类型的信息。

Assembly类:该类位于System.Reflection.Assembly命名空间下面,通过这个类可以访问给定程序集的信息,或者把这个程序集加载到程序中。

四、Type类

Type类位于System.Type命名空间下面,通过这个类可以访问关于任何数据类型的信息。

我们以前把Type看作一个类,但它实际上是一个抽象的基类。只要实例化了一个Type对象,实际上就实例化了Type的一个派生类。尽管一般情况下派生类只提供各种Type方法和属性的不同重载,但是这些方法和属性返回对应数据类型的正确数据,Type有与每种数据类型对应的派生类。

它们一般不添加新的方法或属性。通常,获取指向任何给定类型的Type引用有3种常用方式:

1、使用GetType()方法,所有的类都会从System.Object继承这个方法

string v = "abc";
Type type = v.GetType();

2、使用Type类的静态方法GetType()

Type type2 = Type.GetType("System.string", false, true);

3、使用C#的typeof运算符,这个运算符的参数是类型的名称(但不放在引号中)

var t = typeof(string);

运行结果:

注意:在一个变量上调用GetType()方法,不是把类型的名称作为其参数。但要注意,返回的Type对象仍只与该数据类型相关。如果引用了一个对象,但不能确保该对象实际上是哪个类型的实例,这个方法就很有用。

4、Type类的属性

由Type实现的属性可以分为下述三类:

1)许多属性都可以获取包含与类相关的各种名称的字符串

2)属性还可以进一步获取Type对象的引用,这些引用表示相关的类

3)许多Boolean 属性表示这个类型是一个类、还是一个枚举等。这些属性包括IsAbstract、IsArray、IsClass、IsEnum、IsInterface、IsPointer、IsPrimitive(一种预定义的基本数据类型)、 IsPublic、IsSealed和IsValueType

5、Type类的方法

System.Type类的大多数方法都用于获取对应数据类型的成员信息:构造函数、属性、方法和事件等。它有许多方法,但它们都有相同的模式。

例如,有两个方法可以获取数据类型的方法信息:GetMethod() 和 GetMethods()。GetMethod()方法返回System.Reflection.MethodInfo对象的一个引用,其中包含一个方法的信息。GetMethods()返回这种引用的一个数组。其区别是GetMethods()返回所有方法的信息,而GetMethod()返回一个方法的信息,其中该方法包含特定的参数列表。这两个方法都有重载方法,该重载方法有一个附加的参数,BindingFlags枚举值,表示应返回哪些成员,例如,返回公有成员、实例成员和静态成员等。

Type的成员方法:

注意:GetMember() 和 GetMembers()方法返回数据类型的一个或所有成员的信息,这些成员可以是构造函数、属性和方法等。最后要注意,可以调用这些成员,其方式是调用Type的InvokeMember()方法,或者调用MethodInfo, PropertyInfo和其他类的Invoke()方法。

五、Assembly类

Assembly类在System.Reflection名称空间中定义,它允许访问给定程序集的元数据,它也包含可以加载和执行程序集(假定该程序集是可执行的)的方法。与Type类一样,Assembly类包含非常多的方法和属性。

在使用Assembly实例做一些工作前,需要把相应的程序集加载到正在运行的进程中。为此,可以使用静态成员Assembly.Load()或Assembly.LoadFrom()这两个方法的区别是:

Load()方法的参数是程序集的名称,运行库会在各个位置上搜索该程序集,试图找到该程序集,这些位置包括本地目录和全局程序集缓存。使用Load()方法前要添加程序集的引用。

LoadFrom()方法的参数是程序集的完整路径名,它不会在其他位置搜索该程程序集。

例如:

Assembly assembly1 = Assembly.Load("Animal");
Assembly assembly1 = Assembly.LoadFrom(@"D:\Study\Practice\Animal.dll");

这两个方法都有许多其他重载版本,它们提供了其他安全信息。加载了一个程序集后,就可以使用它的各种属性进行查询,例如,查找它的全名:

string name = assembly1.FullName;

Assembly类的一个功能是它可以获得在相应程序集中定义的所有类型的详细信息,只要调用Assembly以GetTypes()方法,它就可以返回一个包含所有类型的详细信息的Type类型的引用数组:

Type[] types = assembly.GetTypes();
foreach (Type definedType in types)
(
  //处理代码
)

六、使用反射实现上面的程序

经过上面的讲解,相信大家对反射有一定的了解了,下面将会使用反射实现开篇提到的应用场景,代码如下:

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

namespace ReflectionCon
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请录入动物类型:");
            string type = Console.ReadLine().Trim();

            // 创建程序集对象,静态加载Animal程序集  前提:需要先添加对Animal程序集的引用
            Assembly assembly = Assembly.Load("Animal");
            // 获取程序集中的类型(在这里指的就是Animal里面的类:即Cat、Dog、Pig、Bird类)
            Type[] types = assembly.GetTypes();
            foreach (Type t in types)
            {
                // t.Name表示类名(即Cat、Dog、Pig、Bird)
                if (type == t.Name.ToLower())
                {
                    // 找到Shout方法
                    MethodInfo m = t.GetMethod("Shout");
                    // 创建对象
                    object o = Activator.CreateInstance(t);

                    // 找属性
                    PropertyInfo[] para = t.GetProperties();
                    // 遍历属性
                    foreach (PropertyInfo p in para)
                    {
                        // 输出属性的名字 即:Name和Age
                        //Console.WriteLine(p.Name);
                        if (p.Name == "Name")
                        {
                            // 给属性赋值
                            p.SetValue(o, "张三", null);
                        }
                        if (p.Name == "Age")
                        {
                            // 获取o对象的属性为p的属性值并加10
                            int age = Convert.ToInt32(p.GetValue(o)) + 10;
                            // 给属性赋值
                            p.SetValue(o, age, null);
                        }
                    }

                    // 调用方法
                    m.Invoke(o, null);
                }
            }

            Console.ReadKey();
        }
    }
}

运行程序:

如果新增加一个动物类,只需要实现Animal抽象父类即可,而主程序不需要修改。

七、反射的优缺点

1、反射的优点

1)、反射提高了程序的灵活性和扩展性。
2)、降低耦合性,提高自适应能力。
3)、它允许程序动态创建和控制任何类的对象,无需提前硬编码目标类。适用在程序集不固定的地方,通常和配置文件一起使用。

2、反射的缺点

1)、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
2)、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

到此这篇关于C#反射机制的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • c#使用dynamic类型优化反射的方法

    什么是dynamic类型? 微软给出的官方文档中这样解释:在通过 dynamic 类型实现的操作中,该类型的作用是绕过编译时类型检查. 改为在运行时解析这些操作. dynamic 类型简化了对 COM API(例如 Office Automation API).动态 API(例如 IronPython 库)和 HTML 文档对象模型 (DOM) 的访问.在大多数情况下,dynamic 类型与 object 类型的行为类似. 但是,如果操作包含 dynamic 类型的表达式,那么不会通过编译器对该

  • C#高效反射调用方法类实例详解

    C#高效反射调用方法类 1.创建一个业务类(HomeService),在类下创建3个方法 2.正常方式调用类的方法 3.反射方式调用类的方法 4.调用代码 5.调用结果 6.Service类方法代码 内容扩展: 1.正常方式调用类的方法 /// <summary> /// 正常调用类的方法(parm1) /// </summary> /// <returns></returns> public string GetNormalMethod_2() { Hom

  • C#反射调用拓展类方法实例代码

    目录 C# 类拓展方法 C#反射调用拓展类 总结 今天封装Protobuf封包时候遇到一个问题: Protobuf的反序列化方法MergeFrom,是写在扩展类里的:c#拓展类 C# 类拓展方法 要求: 扩展方法类必须为静态类: 拓展方法必须为静态方法,参数为this+需拓展类对象: 多个类拓展方法可以写在一个拓展类中: public class TestExtension { public string Test1() { return "test"; } } public stat

  • C# WPF如何反射加载Geometry几何图形数据图标

    相信大家在阅读WPF相关GitHub开源项目源码时都会看见一串串这种数据 这种Geometry数据就是几何图形数据 为什么要用Geometry数据做图标? 有一种做法是使用ttf字体文件代替,不过使用ttf字体文件会出现下面几个缺点: 1.团队协作不便于管理 2.需要依赖特定平台 3.无法灵活使用 而使用Geometry的话,我们可以将这些几何图形数据存入资源字典ResourceDictionary 通过反射进行灵活使用,团队开发可共同维护 怎么获取Geometry数据? 我们进入https:/

  • C# 通过反射获取类型的字段值及给字段赋值的操作

    举例: 存在一个类: Public Class Student { public string name; public int age; } Student stu1 = new Student(); 现在,我们想通过反射在运行时给stu1的name 和 age字段 赋值,让name = "小明",age = 15,怎么做? 简单的代码如下: ...略 using System.Reflection;//反射类 ...略 static void Main(string[] args)

  • C# 反射与 Quartz 实现流程处理详情

    目录 1.实现 2.创建实例,并执行方法 1.实现 这里主要用的是反射的方法.用户要传入方法名和方法参数,我们就需要先写函数返回这些信息,最后再包装一下返回给用户. 获取某一程序集下所有类:(对我来说,获取当前程序集下的类就够了,要获取其他程序集或dll的,请查询其他资料) public List<string> GetClass(string assembyName = null) { Assembly asm = Assembly.GetExecutingAssembly(); var a

  • C#中通过反射将枚举元素加载到ComboBo的实现方法

    目录 一.前言 二.思路 三.上代码 一.前言 做过系统参数设置的同学们,肯定遇到过要提供一系列具有相同特点的选项供用户选择.最初级的做法是在窗体上增加一个下拉框控件,手工填写Items选项.然后运行时可以下拉选择.那如果有百八十个参数都是这种方式怎么办? 上述做法弊端很明显.那么如何灵活的实现这个需求呢? 二.思路 在代码中定义枚举类型,然后在窗体加载时,将枚举类型的元素(描述信息)加载到下拉框中,这样以后增加或修改了枚举元素后,下拉框中时刻保持的是最新的数据.再运用上反射机制,多个下拉框可以

  • C#使用反射(Reflect)获取dll文件中的类型并调用方法

    使用反射(Reflect)获取dll文件中的类型并调用方法,具体内容如下 需引用:System.Reflection; 1. 使用反射(Reflect)获取dll文件中的类型并调用方法(入门案例) static void Main(string[] args) { //dll文件路径 string path = @"D:\VS2015Project\001\Computer\bin\Debug\computer.dll"; //加载dll文件 Assembly asm = Assemb

  • C#反射使用方法过程及步骤

    C#反射使用方法过程及步骤,供大家参考,具体内容如下 1. 定义要访问类的全名 2. 获取该类的类型 3. 实例化该类 4. 获取该类的字段.属性,方法 5. 设置该字段或属性内容,或调用其方法 从而达到使用字符串访问相应类的目的. 示例: 1. 根据窗口类的名称,产生一个新的窗口,相当于new 窗口类 //1. 定义窗口类名称:(窗口类的字符串名字,需要全路径名,否则获取不到TYPE) string customClassName = @"IBAutoDeal.IBDealForms.&quo

  • c# 反射用法及效率对比

    反射实例化类 public class Person { public string Name { get; set; } public Person(string name) { this.Name = name; } public string Say(string msg) { return $"{Name}: {msg}"; } } class Program { // 测试次数 const int count = 10000000; static void Main(stri

随机推荐