反射

1.反射产生的背景

    对无法直接添加引用的程序集中类型元素的动态获取和使用。使用场景如插件开发,vs本身的智能提示。

2.反射的基本原理

  依托于元数据,运行时动态获取并构建程序集、模块、类型及字段等目标对象并调用目标对象(如调用方法,属性赋值)的机制

    元数据,就是描述数据的数据。在CLR中,元数据就是对一个模块定义或引用的所有东西的描述系统。

               也就是代码架构,vs中选择一个类型,按f12转到定义。

3.反射的作用

     1、查看和遍历类型(及其成员)的基本信息和程序集元数据(metadata);

     2、迟绑定(Late-Binding)方法和属性。3、动态创建类型实例(并可以动态调用所创建的实例的方法、字段、属性)

4 .反射的基础--运行时类型标识

  • 运行时类型标识(RTTI),可以在程序执行期间判定对象类型。
  • 运行时类型标识,能预先测试某个强制类型转换操作,能否成功,从而避免无效的强制类型转换异常。 
  • 在c#中有三个支持RTTI的关键字:is 、 as  、typeof。 
  •  is运算符:

    通过is运算符,能够判断对象类型是否为特顶类型,如果两种类型是相同类型或者两者之间存在引用,装箱拆箱转换,则表明两种类型是兼容的。

  •  as运算符:

     在运行期间执行类型转换且能够使得类型转换失败不抛异常,而返回一个null值。

  • typeof运算符: 

     as ,is 能够测试两种类型的兼容性。但大多数情况下需要获得某个类型的具体信息就用到了typeof,它返回与具体类型相关的System.Type对象一旦获得给定类型的Type对象,就可以通过使用该对象定义的各种属性,字段,方法来获取类型的具体信息

4 .反射的核心类:System.Type

这个类封装了关于对象的信息,获得了类型的Type对象后,就可根据Type提供的属性和方法获取此类型的一切信息(方法、字段、属性、事件、参数、构造函数等)

获取Type对象有两种形式,一种是获取当前加载程序集中的类型(Runtime),一种是获取没有加载的程序集的类型。

我们先考虑Runtime时的Type,一般来说有三种获取方法:

2.1使用Type类提供的静态方法GetType()

Type t = Type.GetType("System.IO.Stream");
txtOutput.Text = t.ToString();

注意到GetType方法接受字符串形式的类型名称。

2.2 使用 typeof 操作符

// 如果在页首写入了using System.IO; 也可以直接用 typeof(Stream);
Type t = typeof(System.IO.Stream);

这时的使用有点像泛型,Stream就好像一个类型参数一样,传递到typeof操作符中。

2.3 通过类型实例获得Type对象

String name = "Jimmy Zhang";
Type t = name.GetType();

使用这种方法时应当注意,尽管我们是通过变量(实例)去获取Type对象,但是Type对象不包含关于这个特定对象的信息,仍是保存对象的类型(String)的信息。

 

5 .反射程序集

在.Net中,程序集是进行部署、版本控制的基本单位,它包含了相关的模块和类型,只是讲述如何通过反射获取程序集信息。

在System.Reflection命名空间下有一个Assembly类型,它代表了一个程序集并包含了关于程序集的信息。

在程序中加载程序集时,我们可以使用 Assembly类型提供的静态方法LoadFrom() 和 Load(),比如:

Assembly asm = Assembly.LoadFrom("Demo.dll");

或者

Assembly asm = Assembly.Load("Demo");

当使用LoadFrom()方法时提供的是程序集的文件名,当将一个程序集添加到项目引用中以后,可以直接写“文件名.dll”。

如果想加载一个不属于当前项目的程序集,则需要给出全路径,比如:

Assembly asm = Assembly.LoadFrom(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Web.dll");

使用Load()方法的时候,只用提供程序集名称即可,不需要后缀名。

如果想获得当前程序集,可以使用Assembly类型的静态方法 GetExecutingAssembly,它返回包含当前执行的代码的程序集(也就是当前程序集)。

Assembly as = Assembly.GetExecutingAssembly();

在获得一个Type类型实例以后,我们还可以使用该实例的Assembly属性来获得其所在的程序集:

Type t = typeof(int)
Assembly asm = t.Assembly;

 一个程序集可能有多个模块(Module)组成,每个模块又可能包含很多的类型,但.Net的默认编译模式一个程序集只会包含一个模块。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
[Serializable]
class SimpleClass
{
    private String _MyString;
    public SimpleClass(String mystring)
    {
        _MyString = mystring;
    }
 
    public override string ToString()
    {
        return _MyString;
    }
 
    static void Main(string[] args)
    {
        Console.WriteLine("简单程序集");
        Console.Read();
    }
}
 
public class AnalyseHelper
{
    /// <summary>
    /// 分析程序集
    /// </summary>
    public static void AnalyzeAssembly(Assembly assembly)
    {
        Console.WriteLine("程序集名字:" + assembly.FullName);
        Console.WriteLine("程序集位置:" + assembly.Location);
        Console.WriteLine("程序集是否在GAC中:" +
                    assembly.GlobalAssemblyCache.ToString());
        Console.WriteLine("包含程序集的模块名" +
            assembly.ManifestModule.Name);
        Console.WriteLine("运行程序集需要的CLR版本:" +
            assembly.ImageRuntimeVersion);
        Console.WriteLine("现在开始分析程序集中的模块");
        Module[] modules = assembly.GetModules();//assembly也可以直接获取所有类型调用.gettypes()
        foreach (Module module in modules)
        {
            AnalyzeModule(module);
        }
    }
 
    /// <summary>
    /// 分析模块
    /// </summary>
    public static void AnalyzeModule(Module module)
    {
        Console.WriteLine("模块名:" + module.Name);
        Console.WriteLine("模块的UUID:" + module.ModuleVersionId);
        Console.WriteLine("开始分析模块下的类型");
        Type[] types = module.GetTypes();
        foreach (Type type in types)
        {
            AnalyzeType(type);
        }
    }
 
    /// <summary>
    /// 分析类型
    /// </summary>
    public static void AnalyzeType(Type type)
    {
        Console.WriteLine("类型名字:" + type.Name);
        Console.WriteLine("类型的类别是:" + type.Attributes);
        if (type.BaseType != null)
            Console.WriteLine("类型的基类是:" + type.BaseType.Name);
        Console.WriteLine("类型的GUID是:" + type.GUID);
        //设置感兴趣的类型成员
        BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
        //分析成员
        FieldInfo[] fields = type.GetFields(flags);
        if (fields.Length > 0)
        {
            //Console.WriteLine("开始分析类型的成员");
            foreach (FieldInfo field in fields)
            {
                // 分析成员
            }
        }
        //分析包含的方法<br>            // MethodInfo类派生于MethodBase抽象类,而MethodBase类继承了MemberInfo类
        MethodInfo[] methods = type.GetMethods(flags);
        if (methods.Length > 0)
        {
            //Console.WriteLine("开始分析类型的方法");
            foreach (MethodInfo method in methods)
            {
                // 分析方法
            }
        }
        //分析属性
        PropertyInfo[] properties = type.GetProperties(flags);
        if (properties.Length > 0)
        {
            //Console.WriteLine("开始分析类型的属性");
            foreach (PropertyInfo property in properties)
            {
                // 分析属性
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
    class Program
    {
        static void Main(string[] args)
        {<br>            //加载程序集
            Assembly assembly = Assembly.LoadFrom(@"..\..\..\SimpleAssembly\bin\Debug\SimpleAssembly.exe");
            AnalyseHelper.AnalyzeAssembly(assembly);
 
            // 创建一个程序集中的类型的对象
            Console.WriteLine("利用反射创建对象");
            string[] paras = { "测试一下反射效果" };
            object obj = assembly.CreateInstance(assembly.GetModules()[0].GetTypes()[0].ToString(), true, BindingFlags.CreateInstance, null, paras, nullnull);
            Console.WriteLine(obj);
 
            Console.ReadKey();
        }
    }

上面代码按照 程序集->模块->类型 三个层次的顺序来动态分析一个程序集,当然还可以继续递归类型内部的成员,最后通过CreateInstance方法来动态创建了一个类型,这些都是反射经常被用来完成的功能,执行结果如下图所示:

本篇是学习反射的一个应用小场景而做的学习笔记,主要是一个小的总结,并对各个步骤的记录,以便将来回顾。

一、基础框架-敏捷基础版本

  这里假定我们要开发一个记事本,选择Windows Form技术开发,界面如下图所示:

Main

  该记事本只提供了一个TextBox供输入,以及保存到指定文件。其他功能均没有实现,假定我们先把这个版本做出来,后续功能通过插件形式一步一步完成。

  但是,为了能够使用插件,我们的主项目还得经过一些改造:

  (1)加载时需要从插件目录中获取插件

复制代码
    public FormMain()
    {
        InitializeComponent();
        // 加载插件
        LoadPlugins();
    }

    private void LoadPlugins()
    {
        // 1.加载plugins目录下的所有的dll文件
        string plugins = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "plugins");
        //   1.1 搜索plugins目录下的所有的dll文件 
        string[] dlls = Directory.GetFiles(plugins, "*.dll");
        // 2.循环将每个dll文件都加载起来
        foreach (string dllPath in dlls)
        {
            //  2.1 动态加载当前循环的dll文件
            Assembly assembly = Assembly.LoadFile(dllPath);
            //  2.2 获取当前dll中的所有的public类型
            Type[] types = assembly.GetExportedTypes();
            //  2.3 获取IEditor接口的Type
            Type typeIEditor = typeof(IEditor);

            for (int i = 0; i < types.Length; i++)
            {
                // 2.4 验证当前的类型即实现了IEditor接口并且该类型还可以被实例化
                if (typeIEditor.IsAssignableFrom(types[i]) && !types[i].IsAbstract)
                {
                    IEditor editor = (IEditor)Activator.CreateInstance(types[i]);
                    // 2.5 向菜单栏中动态添加一个菜单项
                    ToolStripItem toolItem = toolstripEditMenu.DropDownItems.Add(editor.PluginName);
                    // 2.6 为刚刚增加的菜单项注册一个单击事件
                    toolItem.Click += new EventHandler(toolItem_Click);
                    toolItem.Tag = editor;
                }
            }
        }
    }
复制代码

  (2)为插件设置通用的Click事件

复制代码
    private void toolItem_Click(object sender, EventArgs e)
    {
        ToolStripItem item = sender as ToolStripItem;
        if (item != null)
        {
            if (item.Tag != null)
            {
                IEditor editor = item.Tag as IEditor;
                if (editor != null)
                {
                    // 运行该插件
                    editor.Execute(this.txtContent);
                }
            }
        }
复制代码

  这里约定所有插件都实现了IEditor接口,并且所有插件的功能都在Execute方法中被实现。

二、约定接口-可扩展的基础

plugin

  不难发现,如果我们直接使用反射调用dll,即使我们找到了dll文件,也没法知道里面的函数叫什么名字,即使可以枚举出来,也没法智能的调用里面的函数,实现我们预期的功能扩展。于是我们犯难了,我们已经写好的程序哪能预料以后会调用哪些dll的哪些函数呢?

  其实这个并不复杂,我们可以利用接口技术实现这样一种功能。所谓接口,就是一份协议,当大家编写dll时都遵守这样一个协议,那么我们写的dll就可以方便的被exe调用。

  对于这个小demo而言,我们设计一个IEditor接口如下:

复制代码
    public interface IEditor
    {
        string PluginName
        {
            get;
        }

        void Execute(TextBox txtbox);
    }
复制代码

  其中,PluginName是插件的名称,用于菜单显示。Execute方法则接收记事本的TextBox控件,用于实现具体的功能。

三、实现插件-可升级的功能

  (1)插件1:将文本全部转为大写

  新建一个类库项目,设计一个实现IEditor接口的类:

复制代码
    public class ChangeFontStyle : IEditor
    {
        public string PluginName
        {
            get
            {
                return "转为大写";
            }
        }

        public void Execute(TextBox txtbox)
        {
            if (!string.IsNullOrEmpty(txtbox.Text))
            {
                txtbox.Text = txtbox.Text.ToUpper();
            }
            else
            {
                MessageBox.Show("请先输入文字!");
            }
        }
复制代码

  (2)插件2:将文本全部变为红色

  新建一个类库项目,设计一个实现IEditor接口的类:

复制代码
    public class ChangeFontColor : IEditor
    {
        public string PluginName
        {
            get
            {
                return "改变颜色";
            }
        }

        public void Execute(TextBox txtbox)
        {
            if (!string.IsNullOrEmpty(txtbox.Text))
            {
                txtbox.ForeColor = System.Drawing.Color.Red;
            }
            else
            {
                MessageBox.Show("请先输入文字!");
            }
        }
    }
复制代码

四、拥抱变化-简单的测试

  (1)没有任何插件的记事本程序

    Plugins 插件目录下一个dll也木有:

NoPlugins

    因此我们的记事本只有最基本的操作: 

demo1

  (2)加入插件1(转换大写)的记事本程序

    Plugins 插件目录有一个dll:

oneplugin

    这时加入了转换大写的功能:

demo2

  (3)加入插件2(改变颜色)的记事本程序

     Plugins 插件目录有两个dll:

twopluings

     这时加入了改变颜色的功能:

demo3

  由此可知,利用反射和接口,我们可以自定义插件实现不同的扩展功能,让我们的主软件项目更为强大!

posted @ 2016-11-19 17:11  在西天取经的路上……  阅读(164)  评论(0编辑  收藏  举报