您当前的位置: 首页 >  c#

Peter_Gao_

暂无认证

  • 0浏览

    0关注

    621博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

C# 反射及使用场景

Peter_Gao_ 发布时间:2021-04-28 12:10:10 ,浏览量:0

先看下python的反射

一个简单的WEB路由功能,根据不同的url,执行不同的函数,获得不同的页面。

  然而,让我们考虑一个问题,如果commons模块里有成百上千个函数呢(这非常正常)?。难道你在visit模块里写上成百上千个elif?显然这是不可能的!那么怎么破?

三、反射机制

  仔细观察visit中的代码,我们会发现用户输入的url字符串和相应调用的函数名好像!如果能用这个字符串直接调用函数就好了!但是,前面我们已经说了字符串是不能用来调用函数的。为了解决这个问题,python为我们提供一个强大的内置函数:getattr!我们将前面的visit修改一下,代码如下:

import commons
 
def run():
  inp = input("请输入您想访问页面的url: ").strip()
  func = getattr(commons,inp)
  func()
  
if __name__ == '__main__':
  run()

  首先说明一下getattr函数的使用方法:它接收2个参数,前面的是一个对象或者模块,后面的是一个字符串,注意了!是个字符串!

  例子中,用户输入储存在inp中,这个inp就是个字符串,getattr函数让程序去commons这个模块里,寻找一个叫inp的成员(是叫,不是等于),这个过程就相当于我们把一个字符串变成一个函数名的过程。然后,把获得的结果赋值给func这个变量,实际上func就指向了commons里的某个函数。最后通过调用func函数,实现对commons里函数的调用。这完全就是一个动态访问的过程,一切都不写死,全部根据用户输入来变化。

  执行上面的代码,结果和最开始的是一样的。

  这就是python的反射,它的核心本质其实就是利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动!

  这段话,不一定准确,但大概就是这么个意思。

四、进一步完善 上面的代码还有个小瑕疵,那就是如果用户输入一个非法的url,比如jpg,由于在commons里没有同名的函数,肯定会产生运行错误,具体如下:

请输入您想访问页面的url: jpg
Traceback (most recent call last):
 File "F:/Python/pycharm/s13/reflect/visit.py", line 16, in 
  run()
 File "F:/Python/pycharm/s13/reflect/visit.py", line 11, in run
  func = getattr(commons,inp)
AttributeError: module 'commons' has no attribute 'jpg'

那怎么办呢?其实,python考虑的很全面了,它同样提供了一个叫hasattr的内置函数,用于判断commons中是否具有某个成员。我们将代码修改一下:

import commons
  
def run():
  inp = input("请输入您想访问页面的url: ").strip()
  if hasattr(commons,inp):
    func = getattr(commons,inp)
    func()
  else:
    print("404")
  
if __name__ == '__main__':
  run()

  通过hasattr的判断,可以防止非法输入错误,并将其统一定位到错误页面。

  其实,研究过python内置函数的朋友,应该注意到还有delattrsetattr两个内置函数。从字面上已经很好理解他们的作用了。

  python的四个重要内置函数:getattrhasattrdelattrsetattr较为全面的实现了基于字符串的反射机制。他们都是对内存内的模块进行操作,并不会对源文件进行修改。

C#的反射

什么是反射反射就是反着来,映射、影子、copy、clone。。。。假如知道一个类的名字,我要得到它的方法,你知道怎么办吗? 反射就是解决诸如此类的问题 开始:我一个项目里面定义了一个Person 接口,有一个方法叫go()

public interface Person
{
    public void go();
}

然后我定义一个Student类来实现它

public class Student implements Person
{
    public void go()
    {
        System.out.print("student is go........ ");
    }
}

在测试类Test中我就可以这么来了

public class Test 
{
    public static void main(String[] args) throws Exception 
    {
        Person person = new Student();//使用接口来new这个应该知道的吧
        person.go();//会输出student is go........
    }
}

第二步:我这个时候又写了一个教师类Teacher,也实现Person

public class Teacher implements Person
{
    public void go() 
    {
        System.out.print("teacher is go go go ");
    }
}

Test中的代码

public class Test 
{
    public static void main(String[] args) throws Exception 
    {
        Person person = new Teacher();//这里只要改Student为Teacher就可以了,这是多态的好处
        person.go();//会输出teacher is go go go
    }
}

最后:我还不满足,我不想再代码里面改来改去,麻烦,还得找到代码在哪里,这不是浪费时间嘛。所以这个时候,我选择了用配置文件。我在配置文件里写的是Student它就给我来Student的,我换成Teacher它就成teacher is go go go 。只需配置文件改改,这不是很方便嘛。 但是问题就在这里,你在配置文件里就一个student的字符串,它怎么能找到这个类呢,这个时候反射来了,最简单的反射代码Class.forName("****"),就找到了这个类了。当然这个过程中代码太长了,我也懒得打出来。你可以自己去看看文档之类的,我说的仅供参考

反射的缺点:

1.性能问题:使用反射是一种解释操作,远慢于直接代码。 2.程序更加复杂:使用反射会模糊程序的内部逻辑,反射是绕过源代码的技术,因而带来了维护的问题。反射代码比相应的直接代码更复杂。

反射的用途:

类 型    作用Assembly    定义和加载程序集,加载程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的实例。Module    了解包含模块的程序集以及模块中的类等,还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。ConstructorInfo    了解构造器的名称,参数,访问修饰符(如public或private)和实现详细信息(如abstract或virtual)等。使用Type的GetConstructors或GetConstructor方法来调用特定的构造函数。MethodInfo    了解方法的名称,返回类型,参数,访问修饰符(如public或private)和实现详细信息(如abstract或virtual)等。使用type的GetMethods或GetMethod方法来调用特定的方法。FieldInfo    了解字段的名称,方法修饰符(如public或private)和实现详细信息(如static)等,并获取或设置字段值。EventInfo    了解事件的名称,事件处理程序数据类型,自定义特性,声明类型或反射类型等,并添加或移除事件处理程序。PropertyInfo    了解属性的名称,数据类型,声明类型,反射类型和只读或可写状态等,并获取或设置属性值。ParameterInfo    了解参数的名称,数据类型,参数是输入参数还是输出参数等,以及参数在方法签名中的位置等。

常用类 1.取得数据类型Type   方式一:Type.GetType(“类型全名”);  适合于类型的名称已知

 方式二:obj.GetType();  适合于类型名未知,类型未知,存在已有对象

 方式三:typeof(类型)  适合于已知类型

 方式四:Assembly.Load(“XXX”).GetType(“名字”);  适合于类型在另一个程序集中      Type类常用Get系列方法 Is系列属性。  2.MethodInfo(方法)  重要方法: Invoke

 3.PropertyInfo(属性)  重要方法:SetValue GetValue

 4.FieldInfo(字段)  重要方法:SetValue GetValue

 5.ConstructInfo(构造方法)  重要方法:Invoke   动态创建对象 Activator.CreateInstance(string 程序集名称,string 类型全名) Activator.CreateInstance(Type type);

Assembly assembly = Assembly.Load(程序集); assembly.CreateInstance(Type);

//找到有参构造方法,动态调用构造方法 type.GetConstructor(typeof(string)).Invoke() 

举例:

ReflectionClass rc = new ReflectionClass();
Type t = rc.GetType();
object obj = Activator.CreateInstance(t);
//取得ID字段
FieldInfo fi = t.GetField("id");
//给ID字段赋值
fi.SetValue(obj, 2);
//取得Name属性
PropertyInfo piName = t.GetProperty("Name");
//给Name属性赋值
piName.SetValue(obj, "jujianfei", null);
PropertyInfo piAge = t.GetProperty("Age");
piAge.SetValue(obj, "23", null);
//取得Show方法
MethodInfo mi = t.GetMethod("Show");
//调用Show方法
mi.Invoke(obj, null);
Console.WriteLine("ID为:" + ((ReflectionClass)obj).id);

反射的用法实例:

——1.获取程序集信息和类型信息。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
using System;

public class SelectReflectionDemo : MonoBehaviour
{
    public void Method()
    {

    }
    public void Notify()
    {

    }
    public int Count;
    public int Max;
    private string mName;
    private int mLevel;
    public string Name
    {
        get { return mName; }
        set { Name = value; }
    }

    public int Level
    {
        get { return mLevel; }
        set { mLevel = value; }
    }
    void Start()
    {
        List ScrInfos = new List();
        Type my = this.GetType();
        ScrInfos.Add(my.Name);//类名
        ScrInfos.Add(my.Namespace);//所属命名空间
        ScrInfos.Add(my.Assembly.ToString());//程序集信息
        FieldInfo[] fieinfos = my.GetFields();//获取字段集合(public)
        foreach (FieldInfo info in fieinfos)
        {
            ScrInfos.Add(info.ToString());
        }
        PropertyInfo[] proinfos = my.GetProperties();//获取属性集合(public)
        for (int i = 0; i < 2; i++)
        {
            ScrInfos.Add(proinfos[i].ToString());
        }
        MethodInfo[] methinfos = my.GetMethods();//获取所有的方法
        for (int i = 0; i < 2; i++)
        {
            ScrInfos.Add(methinfos[i].ToString());
        }
        foreach (string str in ScrInfos)
        {
            Debug.Log(str);
        }
    }

}

——2.动态创建类型实例并执行其中的方法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Reflection;

public class CreateReflectionDemo : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        //两种创建方式,一种是根据type类型创建,一种是根据字符串创建。
        Type mytype = typeof(MyClass);
        MyClass my = Activator.CreateInstance(mytype) as MyClass;//根据type类型创建
        my.Name = "我是使用type创建的";
        Debug.Log(my.Name);
        //
        string AssemblyName =Assembly.GetExecutingAssembly().GetName().Name;//获取程序集名称
        string strNameSpace= System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Namespace;//获取命名空间名称;
        string strClassName =mytype.Name;
        string fullclassName = strNameSpace + "." + strClassName;//类全名(命名空间.类名称)
        Debug.Log(AssemblyName);
        Debug.Log(fullclassName);
        //需要程序集字符串和类全名字符串(命名空间.类名称)
        MyClass mys = (MyClass)Assembly.Load(AssemblyName).CreateInstance(fullclassName);
        mys.Name = "我是通过字符串创建的";
        Debug.Log(mys.Name);
    }
}
public class  MyClass
{
    public int Count;
    public int Max;
    private string mName;
    private int mLevel;
    public string Name
    {
        get { return mName; }
        set { mName = value; }
    }
    public int Level
    {
        get { return mLevel; }
        set { mLevel = value; }
    }
    public void Method()
    {

    }
    public void Notify()
    {

    }
}

反射包含的东西还是非常多的,首先在这里准备了一些简单的代码,有个Model类库,数据访问层的DB.SqlServer类库,数据访问层接口DB.Interface类库,以及MyReflection的控制台程序,其中DB.SqlServer继承DB.Interface:

我们想在Program的Main方法中调用DB.SqlServer类库中SqlServerHelper类中的查询方法Query。以往实现这种功能都是在MyReflection的控制台程序中引用DB.SqlServer和DB.Interface类库,才能调用Query方法,如下图所示:

以上这种方式调用其他类库的方法,相信对大家来说没什么难度,应该都会的。

关于dll---->IL---->Metadata---->反射 在讲反射之前,我们先来看下我们写的代码究竟是如何被计算机执行的,这里给大家画了个图,如下所示:

对于计算机来讲,它只认识01010101之类的二进制代码,人类写的高级语言(如C#、JAVA等)计算机是没法识别的,所以需要将高级语言转化为01让计算机可以识别的二进制编码,中间是有一个过程的。就拿C#来讲,VS编译器会将编写好的代码进行编译,编译后会生成exe/dll文件,.Net Core里面已经不生成exe了,都是dll。dll和exe还需要CLR/JIT的即时编译成字节码,才能最终被计算机执行。

有伙伴就会问为什么要编译2次呢,先编译到dll,再编译到字节码01呢,为什么不能一次性编译成字节码呢?因为我们写的是C#语言,但是真实运行的机器有很多种,可能是32位,也可能是64位,操作系统可能是windows、linux、unix等,不同的计算机不同的操作系统识别字节码的可能是不一样的,但是从高级语言编译成exe/dll这一步是一样的。所以只要在不同运行环境的计算机上安装对应的不同的CLR/JIT,就可以运行我们同一个exe/dll了。这里就大概讲下这样一个过程,后面会有章节详细讲解程序如何被计算机执行的。

现在我们先关注编译生成的exe/dll,它包含2部分,分别是中间语言IL和源数据元数据metadata。IL里面包含我们写的大量的代码,比如说方法、实体类等。元数据metadata不是我们写的代码,它是编译器在编译的时候生成的描述,它可能是把命名空间、类名、属性名记录了一下,包括特性。

反射加载dll,读取module、类、方法、特性 讲上面程序的编译过程跟反射有什么关系呢?我们反射就是读取metadata里面的数据的,然后去使用它。接下来通过反射的方式去实现调用DSqlServerHelper类中的查询方法Query:

反射创建对象(反射+简单工厂+配置文件) 可以从图中结果看出利用反射成功调用了DSqlServerHelper类下的查询方法Query。对比下最开始添加引用直接new SqlServerHelper()对象然后调取Query方法写的代码更少些,而利用反射需要多写好几行代码,貌似反射更麻烦些,那什么还要用反射呢?如果把using DB.SqlServer注释掉,就会发现new SqlServerHelper()会报错,而不会影响反射写的代码。也就是说反射在不添加引用,不new一个对象的情况下,可以动态的调取对象中的方法,这就是反射的好处。另外觉得这里反射的代码太多了,那是因为没封装,接下来封装下再去调用代码就少很多了。 


    

namespace MyReflection
{
    public class SimpleFactory
    {
        private static string TypeDll = ConfigurationManager.AppSettings["IDBHelper"];
        private static string DllName = TypeDll.Split(',')[1];
        private static string TypeName = TypeDll.Split(',')[0];
        public static IDBHelper CreateHelper()
        {
            Assembly assembly = Assembly.Load(DllName);
            Type typeDBHelper = assembly.GetType(TypeName);
            object oDBHelper = Activator.CreateInstance(typeDBHelper);
            IDBHelper iDBHelper = oDBHelper as IDBHelper;
            return iDBHelper;
        }
    }
}

反射只能做这点事吗,仅仅就这么简单吗?当然不是的,现在是使用的sqlserver类库,假如程序需要扩展了,现在要从sqlserver换成mysql,或者是oracle。如果之前是用的添加引用类库的方式,然后new SqlServerHelper()对象去调用Query(),现在就要重新添加DB.MySql引用,然后改命名空间,并把new SqlServerHelper()改成new MySqlHelper()了。

但是在反射的做法里面就不一样了,我们只需要简单的升级下,把配置文件之前sqlserver的配置注释,改成mysql的就可以了:


    
	

反射的黑科技(多构造函数调用、破坏单例、创建泛型) 1、多构造函数的调用

上面反射都是调用类的无参构造函数,

2、调用私有的构造函数(破坏单例)

先说说单例模式,单例模式就是为了在我们的进程中

3、创建泛型

反射调用实例方法、静态方法、重载方法 1、调用实例方法

2、调用静态方法

3、调用重载方法

  

深入浅出C#反射(Reflection)原理和应用场景_ananlele_的专栏-CSDN博客_c# 反射使用场景

C#反射详解_习惯成自然-CSDN博客_c# 反射

C#之玩转反射 - y-z-f - 博客园

关注
打赏
1664521772
查看更多评论
立即登录/注册

微信扫码登录

0.0376s