CH03_反射

第3章:反射


本章目标

  1. 掌握反射的原理

  2. 熟悉反射的基本运用

本章内容

反射是什么

C# 编译运行过程
  • 首先我们在VS点击编译的时候,就会将C#源代码编译成程序集

    • 程序集以可执行文件 (.exe) 或动态链接库文件 (.dll) 的形式实现

  • 程序集中包含有Microsoft 中间语言 (MSIL) 和必需的元数据。

    • 元数据存储以下信息:

      • 程序集的说明:标识(名称、版本、区域性、公钥)、导出的类型、该程序集所依赖的其他程序集、运行所需的安全权限。
      • 类型的说明:名称、可见性、基类和实现的接口、成员(方法、字段、属性、事件、嵌套的类型)。
      • 特性:修饰类型和成员的其他说明性元素。
  • 在执行时,实时 (JIT) 编译器将 MSIL 转换为本机代码

    • 运行 Microsoft 中间语言 (MSIL) 前,必须根据公共语言运行时将其编译为目标计算机基础结构的本机代码。

  • 运行代码

    • 公共语言运行时提供启用要发生的托管执行的基础结构以及执行期间可使用的服务

反射的工作原理

反射 来自 System.Reflection命名空间,它可以读取程序集中的元数据,利用元数据创建对象,从而实现各种功能。

提示:

区分 反射 与反编译,反射读取的是元数据,反编译读取的IL代码

反射的优缺点
  • 优点:提高了程序的灵活性和扩展性,降低耦合度
  • 缺点:由于反射多了一道程序,性能上相较于直接代码要慢

反射的使用

反射相关的类和命名空间

反射的命名空间:

c# 复制代码
using System.Reflection;

反射相关的类:

c# 复制代码
System.Type						   //类型
System.AppDomain				   //应用程序域
System.Activator				   //激活器
System.Reflection.Assembly			//程序集
System.Reflection.Module			//模块


System.Reflection.ConstructorInfo	//构造函数
System.Reflection.ParameterInfo		//方法参数
System.Reflection.MethodInfo		//方法
System.Reflection.PropertyInfo		//属性
System.Reflection.FieldInfo			//字段
System.Reflection.MemberInfo		//成员
Type类的应用
Type类中的基本属性
c# 复制代码
/// <summary>
/// 学生类
/// </summary>
class Student
{
    private int _num = 0;

    public string Phone = "15818704257";

    public string Name { get; set; }

    public string Address { get; set; }

    public Student()
    {
        //Console.WriteLine("Student 默认构造函数");
    }

    public Student(string name)
    {
        //Console.WriteLine($"Student 参数化构造函数:{name}");
    }

    public Student(string name,string phone,string address)
    {

    }

    public int PublicMethod()
    {
        return int.MinValue;
    }

    internal void InternalMethod()
    {

    }
    private void PrivateMethod()
    {

    }

    public void Show(int id)
    {
        Console.WriteLine("调用了Show()方法!"+id);
    }
}

class MyArray<T>
{

}

/// <summary>
/// USB接口
/// </summary>
interface IUsb
{

}

struct Teacher
{

}
c# 复制代码
/// <summary>
/// Type类的基本属性
/// </summary>
static void Fun1()
{
    Type t0 = typeof(MyArray<int>);
    Type t1= typeof(Student);
    Type t2 = typeof(IUsb);
    Type t3 = typeof(Teacher);
    Type t4 = typeof(string);

    Console.WriteLine("名称:"+t1.Name);
    Console.WriteLine("全名:"+t1.FullName);
    Console.WriteLine("命名空间:"+t1.Namespace);

    Console.WriteLine("是否是抽象的:"+t1.IsAbstract);
    Console.WriteLine("是否是公共的:" + t1.IsPublic);

    Console.WriteLine("是否是类:" + t1.IsClass);
    Console.WriteLine("是否是枚举:" + t1.IsEnum);
    Console.WriteLine("是否是接口:" + t1.IsInterface);

    Console.WriteLine("是否是嵌套定义的:" + t1.IsNested);
    Console.WriteLine("是否是值类型:" + t1.IsValueType);
    Console.WriteLine("是否是泛型类型:" + t1.IsGenericType);
}
c# 复制代码
using ClassLibrary1;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace CH03Demo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Fun11();

            Console.ReadLine();
        }
    }
}

提示:

FullName :获取该类型的完全限定名称,包括其命名空间,但不包括程序集 。

Type类的Assembly属性
c# 复制代码
/// <summary>
/// Type类的Assembly属性
/// </summary>
static void Fun2()
{

    Type type = typeof(Student);

    //程序集
    Assembly a1 = type.Assembly;

    Console.WriteLine("位置:" + a1.CodeBase);
    Console.WriteLine("全名:" + a1.FullName);

    Console.WriteLine("--------------------------------");

    Type t1 = typeof(Student);
    Type t2 = typeof(string);

    Console.WriteLine("全名:" + t1.FullName);
    Console.WriteLine("全名:" + t2.FullName);

    //查看程序集限定名
    Console.WriteLine(t1.AssemblyQualifiedName);
    Console.WriteLine(t2.AssemblyQualifiedName);

}
Type类对象获取构造函数
c# 复制代码
/// <summary>
/// 查看构造函数
/// </summary>
static void Fun3()
{
    Type t= typeof(Student);

    ConstructorInfo[] ciArray= t.GetConstructors();

    //遍历构造函数
    foreach (ConstructorInfo ci in ciArray)
    {
        Console.WriteLine("构造函数名:" + ci.Name);

        foreach (ParameterInfo item in ci.GetParameters())
        {
            Console.WriteLine("参数:{0},类型:{1}", item.Name, item.ParameterType);
        }
        Console.WriteLine("-------------------------------");
    }
}
Type类对象获取方法
c# 复制代码
/// <summary>
/// 查看当前实例的所有public方法
/// </summary>
static void Fun4()
{
    Type t = typeof(Student);

    MethodInfo[] miArray = t.GetMethods();

    //遍历构造函数
    foreach (MethodInfo mi in miArray)
    {
        Console.WriteLine("方法名:{0},返回类型:{1}",mi.Name,mi.ReturnType);

        foreach (ParameterInfo item in mi.GetParameters())
        {
            Console.WriteLine("参数:{0},类型:{1}" ,item.Name,item.ParameterType);
        }
        Console.WriteLine("-------------------------------");
    }
}
Type类对象获取属性
c# 复制代码
/// <summary>
/// 查看当前实例的所有public属性
/// </summary>
static void Fun5()
{
    Type t = typeof(Student);

    PropertyInfo[] miArray = t.GetProperties();

    //遍历
    foreach (PropertyInfo pi in miArray)
    {
        Console.WriteLine("属性名:{0},类型:{1}",pi.Name,pi.PropertyType);

        Console.WriteLine("-------------------------------");
    }
}
Type类对象获取字段
c# 复制代码
/// <summary>
/// 查看当前实例的所有public字段
/// </summary>
static void Fun6()
{
    Type t = typeof(Student);

    FieldInfo[] fiArray = t.GetFields();

    //遍历
    foreach (FieldInfo fi in fiArray)
    {
        Console.WriteLine("字段名:{0},类型:{1}", fi.Name, fi.FieldType);

        Console.WriteLine("-------------------------------");
    }
}
Type类对象获取成员
c# 复制代码
/// <summary>
/// 查看当前实例的所有public成员
/// </summary>
static void Fun7()
{
    Type t = Type.GetType("CH03Demo.Student"); //typeof(Student);

    MemberInfo[] miArray = t.GetMembers();
    
    //遍历
    foreach (MemberInfo mi in miArray)
    {
        Console.WriteLine("成员名:{0},类型:{1}", mi.Name, mi.MemberType);

        Console.WriteLine("-------------------------------");
    }
}
使用BindingFlags筛选成员
c# 复制代码
/// <summary>
/// 使用绑定标志枚举筛选成员
/// </summary>
static void Fun8()
{
    Type t = Type.GetType("CH03Demo.Student"); //typeof(Student);

    //GetMembers 中传入 BindingFlags 相当于是对成员信息进行一个过滤
    //BindingFlags 不仅仅是GetMembers 专有,很多方法中都可以传入BindingFlags进行过滤

    //BindingFlags 是位标志枚举,可使用 | & ^ 等运算符, | 表示取并集,& 表示取交集,^ 表示取差集
    //BindingFlags.Public 表示公共成员
    //BindingFlags.NonPublic 表示非公共成员
    //BindingFlags.Instance 表示实例成员
    //BindingFlags.Static 表示静态成员
    MemberInfo[] miArray = t.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance);

    //遍历
    foreach (MemberInfo mi in miArray)
    {
        Console.WriteLine("成员名:{0},类型:{1}", mi.Name, mi.MemberType);

        Console.WriteLine("-------------------------------");
    }
}

提示:

BindingFlags.Instance 和BindingFlags.Static :实例成员是相对于静态成员而言的,多数情况下我们都省略了BindingFlags 这个参数,少数需要筛选成员的时候,传入该参数

获取Type实例的方式
c# 复制代码
/// <summary>
/// 获取Type实例的方式
/// </summary>
static void Fun9()
{
    Student stu = new Student();

    //方式1:System.Type类的ComDefaultInterface特性
    Type t1 = typeof(Student);

    //方式2:System.Object中的GetType()方法
    Type t2 = stu.GetType();

    //方式3:System.Type中GetType()方法
    //想通程序集,传入FullName即可(命名空间+类名)
    Type t3 = Type.GetType("CH03Demo.Student");
    //不同程序集,则还需传入程序集名
    Type t4 = Type.GetType("ClassLibrary1.Class1,ClassLibrary1");


}
Activator类的应用
c# 复制代码
//Activator类主要用于创建对象的实例
Type type = typeof(UserInfo);
UserInfo userInfo=(UserInfo)Activator.CreateInstance(type);
Assembly类的应用
  • 对于程序集的限定名称使用小结
    • 程序集的显示名称,可通过Assembly.FullNameAssembly.GetName().FullName(即AssemblyName.FullName) 两种方式获取,这种获取的名称,一般是作为 Assembly.Load()的标准参数值
    • 类型的程序集限定名,可通过Type类中的AssemblyQualifiedName属性获取(通常作为Type.GetType()方法中的参数值), 相较于Assembly.FullName,名称格式上多了 Type.FullName 这一部分
  • Assembly类中的常用方法
    • Assembly.Load()方法接收一个String或AssemblyName类型作为参数,这个参数需要程序集的强名称
    • Assembly.LoadFrom() 根据程序集的文件名或路径,加载程序集;这个方法会加载此程序集引用的其他程序集
    • Assembly.LoadFile() 加载指定路径上的程序集文件内容,和上面方法的不同之处是这个方法不会加载此程序集引用的其他程序集

程序集的强名称:是程序集的FullName(具有名称,版本,语言,公钥标记);

程序集的弱命名:只有程序集名称而没有版本,语言和公钥标记;平常我们创建的一个类库,如果没有特殊操作都属于是是弱名称程序集

Load("强名称程序集")查找程序集的顺序:首先它会去全局程序集缓存查找,然后到应用程序的根目录查找,最后会到应用程序的私有路径查找。

Load("弱名称程序集")查找程序集的顺序:首先到应用程序的根目录查找,最后会到应用程序的私有路径查找。

c# 复制代码
 /// <summary>
 /// 程序集加载的3种方式
 /// </summary>
 static void Fun12()
 {
     //方式1:将ClassLibrary1.dll放在根目录下,传入程序集的简单名称,即可加载
     Assembly assembly1 = Assembly.Load("ClassLibrary1");

     //方式2:传入ClassLibrary1.dll的文件路径,会加载ClassLibrary1.dll依赖的程序集
     Assembly assembly2 = Assembly.LoadFrom("ClassLibrary1.dll");

     //方式3:传入ClassLibrary1.dll的文件路径,不会加载ClassLibrary1.dll依赖的程序集
     Assembly assembly3 = Assembly.LoadFile(AppDomain.CurrentDomain.BaseDirectory+ "ClassLibrary1.dll");
 }
Module类的应用
什么是模块

System.Reflection.Module类‌是C#中的一个重要类,它提供了有关程序集中模块的信息和功能。这个类的作用和使用方法在深入探讨模块类时会被详细讨论,通过一些示例代码来展示它的功能。在C#中,模块可以是一个源代码文件、一个编译后的文件(DLL或EXE)或者一个动态生成的程序集。每个模块都有自己的元数据和IL代码,而System.Reflection.Module类提供了访问和操作这些模块的能力。通过Module类,我们可以获取模块的元数据、类型信息、成员信息以及执行模块中的代码。

模块的属性
c# 复制代码
/// <summary>
/// 模块
/// </summary>
static void Fun13()
{
    //类型对象
    Type t1 = typeof(Student);
    Type t2 = typeof(string);

    //模块
    Module module1 = t1.Module;
    Module module2 = t2.Module;

    //基本属性
    Console.WriteLine("模块名:"+module1.Name);
    Console.WriteLine("完全限定名:" + module1.FullyQualifiedName);
    Console.WriteLine("程序集名:" + module1.Assembly.FullName);

    Console.WriteLine("-----------------------------");

    Console.WriteLine("模块名:" + module2.Name);
    Console.WriteLine("完全限定名:" + module2.FullyQualifiedName);
    Console.WriteLine("程序集名:" + module2.Assembly.FullName);
}
AppDomain类的应用

前提:

从.NET Core开始,不再支持运行时创建其他AppDomain(即仅可在.NET Framework下支持创建其他AppDomain)。

因此以下部分内容仅在.NET Framework上有效。

官方说明:.NET Framework 技术在 .NET 6 及更高版本上不可用

AppDomain应用程序域:一组程序集的逻辑容器,CLR创建的第一个AppDomain称为默认AppDomain,仅在进程终止时销毁。

一个AppDomain可以包含N个Assembly,一个Assembly可以包含N个Module,而一个Module可以包含N个Type.

c# 复制代码
/// <summary>
/// 应用程序域
/// </summary>
static void Fun14()
{
    //获取当前应用程序域中的所有程序集
    Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

    foreach (Assembly assembly in assemblies)
    {
        Console.WriteLine(assembly.FullName);
    }
}
通过反射创建对象
c# 复制代码
/// <summary>
/// 反射:创建对象的3种方式
/// </summary>
static void Fun10()
{
    //类型对象
    Type t = typeof(Class1);

    #region 方式1:通过Invoke 执行构造函数

    //获取构造函数对象
    ConstructorInfo ci1 = t.GetConstructor(new Type[] { });
    ConstructorInfo ci2 = t.GetConstructor(new Type[] {typeof(int) });

    //调用构造函数创建实例
    object obj1= ci1.Invoke(new object[] { });
    object obj2 = ci2.Invoke(new object[] {15 });

    //类型装换
    Class1 c1=obj1 as Class1;
    Class1 c2 = obj2 as Class1;

    #endregion

    #region 方式2:通过Assembly 创建实例

    //程序集对象
    Assembly assembly = Assembly.Load("ClassLibrary1");

    //创建实例
    object obj3 = assembly.CreateInstance("ClassLibrary1.Class1",true);

    //类型转换
    Class1 c3= obj3 as Class1;

    #endregion

    #region 方式3:通过 Activator 创建实例

    //创建对象
    object obj4 = Activator.CreateInstance(t);
    object obj5 = Activator.CreateInstance("ClassLibrary1", "ClassLibrary1.Class1");

    //类型转换
    Class1 c4 = obj4 as Class1;
    Class1 c5 = obj5 as Class1;

    #endregion
}
通过反射获取对象成员
c# 复制代码
/// <summary>
/// 反射:获取 方法、属性、字段
/// </summary>
static void Fun11()
{
    //类型对象
    Type t = typeof(Student);

    //目标实体
    object obj= Activator.CreateInstance(t);

    #region 反射:获取方法

    //获取方法对象
    MethodInfo mi= t.GetMethod("Show");

    //执行方法
    mi.Invoke(obj, new object[] {25 });

    //Invoke 调用静态方法,对象可以为null ,形如 
    //methodInfo.Invoke(null, new object[] { "hello" })

    #endregion

    #region 反射:获取属性

    //获取属性对象
    PropertyInfo pi = t.GetProperty("Name");
    pi.SetValue(obj, "张三");

    //调用属性
    string name= pi.GetValue(obj) as string;
    Console.WriteLine("姓名:"+name);

    #endregion

    #region 反射:获取字段

    //获取属性对象
    FieldInfo fi = t.GetField("Phone");
    fi.SetValue(obj, "13523983345");

    //调用字段
    string phone = fi.GetValue(obj) as string;
    Console.WriteLine("电话:" + phone);

    #endregion
}

反射的应用

数据库辅助类反射
常规情况下:编写固定DBHelper类
c# 复制代码
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH03Demo
{
    internal class DBHelper
    {
        private static readonly string _connectionString = "server=.;database=test;uid=sa;pwd=sa";

        /// <summary>
        /// 执行增删改
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public int ExecuteNonQuery(string sql,params SqlParameter[] parameters)
        {
            //略
            return 0;
        }
        /// <summary>
        /// 执行查询
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        public DataTable ExecuteTable(string sql,params SqlParameter[] parameters)
        {
            //略
            return null;
        }
    }
}
反射+配置文件动态实现

1.创建一个接口

c# 复制代码
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Common;

namespace CH03Demo
{
    /// <summary>
    /// 数据访问接口
    /// </summary>
    internal interface IDBHelper
    {
        /// <summary>
        /// 执行增删改
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        int ExecuteNonQuery(string sql, params DbParameter[] parameters);

        /// <summary>
        /// 执行查询
        /// </summary>
        /// <param name="sql"></param>
        /// <returns></returns>
        DataTable ExecuteTable(string sql, params DbParameter[] parameters);
    }
}

2.实现接口

c# 复制代码
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CH03Demo
{
    /// <summary>
    /// sqlserver 数据库的数据访问类
    /// </summary>
    internal class SqlServerDBHelper : IDBHelper
    {
        public int ExecuteNonQuery(string sql, params DbParameter[] parameters)
        {
            //代码略
            return 0 ;
        }

        public DataTable ExecuteTable(string sql, params DbParameter[] parameters)
        {
            //代码略
            return null;
        }
    }
}

3.增加配置文件

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
	<appSettings>
		<add key="DBType" value="SqlServer"/>
	</appSettings>
</configuration>

4.过反射+配置文件 调用 数据库执行语句的方法

c# 复制代码
/// <summary>
/// 通过反射创建数据辅助类对象
/// </summary>
static void Fun15()
{
    string fullName = ConfigurationManager.AppSettings["DBType"].ToString();

    IDBHelper dbHelper = (IDBHelper)Assembly.Load("DBHelper").CreateInstance(fullName);

    var data1 = dbHelper.ExecuteNonQuery("delete from student where studentNo=@studentNo", new SqlParameter("@studentNo", "GCKJ101"));
    var data2 = dbHelper.ExecuteTable("select * from student");

    Console.WriteLine(data1);
    Console.WriteLine(data2.Rows.Count);
}

目录结构:

本章总结

课后作业

1.通过反射查看int类 Type基本信息

2.通过反射调用int类的实例方法: CompareTo

3.通过反射调用int类的静态方法: Parse

相关推荐
向宇it12 小时前
【从零开始入门unity游戏开发之——C#篇23】C#面向对象继承——`as`类型转化和`is`类型检查、向上转型和向下转型、里氏替换原则(LSP)
java·开发语言·unity·c#·游戏引擎·里氏替换原则
sukalot12 小时前
windows C#-命名实参和可选实参(下)
windows·c#
小码编匠12 小时前
.NET 下 RabbitMQ 队列、死信队列、延时队列及小应用
后端·c#·.net
我不是程序猿儿1 天前
【C#】Debug和Release的区别和使用
开发语言·c#
lzhdim1 天前
2、C#基于.net framework的应用开发实战编程 - 设计(二、二) - 编程手把手系列文章...
开发语言·c#·.net
misty youth1 天前
学生信息管理系统
c语言·数据结构·算法·c#
冒泡P1 天前
【Lua热更新】上篇
开发语言·数据结构·unity·c#·游戏引擎·lua
忒可君1 天前
C# winform 字符串通过枚举类型转成int类型的数据
开发语言·c#
工业3D_大熊1 天前
HOOPS Communicator功能剖析:3D Web模型树交互的实用指南!
linux·windows·macos·3d·docker·c#·.net