c#反射机制

反射是一种机制,通过这种机制我们可以知道一个未知类型的类型信息。比如,有一个对象a,这个对象不是我们定义的,也许是通过网络捕捉到的,也许是使用泛型定义的,但我们想知道这个对象的类型信息,想知道这个对象有哪些方法或者属性什么的。甚至我们想进一步调用这个对象的方法。关键是现在我们只知道它是一个对象,不知道它的类型,自然不会知道它有哪些方法等信息。这时我们该怎么办?反射机制就是解决这么一个问题的。通过反射机制我们可以知道未知类型对象的类型信息。再比如,我们有一个dll文件,我们想调用里面的类,现在假设这个dll文件的类的定义,数量等不是固定的,是经常变化的。也许某一天你要在这个dll里面增加一个类定义。也许你觉得这没什么问题,现在关键是我们在另一个程序集里面要调用这个dll,这时我们的程序必须能够适应这个dll的变化,也就是说即使改变了dll文件的定义也不需要改变我们的程序集。这时候我们就会使用一个未知dll.我们该怎么办?同样,反射机制帮助了我们,我们可以通过反射来实现。
说白了,反射就是能知道我峨嵋你未知类型的类型信息这个一个东西。
例子:
我们有一个dll.该dll里面有许多关于运动的类。每一个类记录了一种体育运动的信息。我们在另外一个程序里面要知道这个dll的信息:
第一步:我们建一个文件Sport.cs.内容如下:

using System;
public abstract class Sport
{
protected string name;
public abstract string GetDuration();
public abstract string GetName();
}

咱们用命令" csc /t:library Sport.cs "编译它.

第二步,我们再建一个名为 SomeSports.cs 的文件,内容如下:

using System;
public class Football:Sport
{
 public Football()
 {
  name="Football";
 }
 public override string GetDuration()
 {
     return "four 15 minute quarters";
 }
 public override string GetName()
 {
     return name;
 }
}
public class Hockey:Sport
{
 public Hockey()
 {
    name="Hockey";
 }
  public override string GetDuration()
    {
        return "three 20 minute periods";
    }
    public override string GetName()
    {
        return name;
    }
}
public class Soccer : Sport
{
    public Soccer()
    {
        name = "Soccer";
    }
    public override string GetDuration()
    {
        return "two 45 minute halves";
    }
    public override string GetName()
    {
        return name;
    }
}

下面我们用命令" csc /t:library /r:Sport.dll SomeSports.cs "编译该文件.

现在我们有了我们的运动信息 dll 文件.现在我们想通过程序知道里面有哪些类.请进入最后一步:
第三步:我们创建文件AssemblyDemo.cs.内容如下:

using System;
using System.Reflection;
public class AssemblyDemo
{
    int i,j;
    //==========================
        //First the command line arguments are evaluated.if there isn't
        //at least one,a usage message is printed
        //=================================
        if (args.GetLength(0) < 1)
        {
            Console.WriteLine("usage is AssemblyDemo<library_name>");
        }
        else
        {
            //========================
            // An Assembly object is obtained from the command line argument
            //========================
            Assembly assembly=Assembly.LoadFrom(args[0]);
            Type[] types=assembly.GetTypes();//获得dll文件里所有的类
            Console.WriteLine(assembly.GetName().Name+"contains the following types");
            for(i=0;i<types.GetLength(0);++i)
            {
            Console.WriteLine("\r("+i+")"+types[i].Name);
            }
            i=types.Length-1;
            Console.Write("make selection(0-"+i+");");
            j=Convert.ToInt32(Console.ReadLine());
            Console.WriteLine();
            if(types[j].IsSubclassOf(typeof(Sport)))
            {
                ConstructorInfo ci=types[j].GetConstructor(new Type[0]);
                Sport sport=(Sport)ci.Invoke(new Object[0]);
                Console.WriteLine(sport.GetName()+"has"+sport.GetDuration());
            }
            else
            {
              Console.WriteLine(types[j].Name+"is not a sub-class of Sport");
            }
            }
            
}

咱们用命令" csc /r:Sport.dll AssemblyDemo.cs "编译该文件.

下面我们用"A ssemblyDemo SomeSports.dll "运行该程序.

进一步程序要求我们输入选项, 咱们输入1,就显示了结果: Hockeyhasthree 20 minute periods.
模块信息是通过 Module 类访问的。下面通过一个类子,讲解下 Module 类的使用,如果你是

一个用心的程序员,应该了解下 Module 的详细信息。

下面我们写一个新的文件 ModuleDemo.cs 。内容如下:

// 编译命令 csc /r:Sport.dll ModuleDemo.cs

using System;
using System.Reflection;
public class ModuleDemo
{
    public static void Main(string[] args)
    {
        //=======================
        // Am Module object is obtained representing the
        // SomeSports.dll library file
        //=======================
        Assembly assembly = Assembly.Load("SomeSports");
        Module module = assembly.GetModule("SomeSports.dll");
        //======================
        //Search the module for the type named "Football"
        Type[] types = module.FindTypes(Module.FilterTypeName, "Football");
        if (types.Length != 0)
        {
            ConstructorInfo ci = types[0].GetConstructor(new Type[0]);
            Sport sport = (Sport)ci.Invoke(new Object[0]);
            Console.WriteLine(sport.GetName() + " has " + sport.GetDuration());
        }
        else
        {
            Console.WriteLine("type not found");
        }
    }
}

总结:
首先搞清楚以下命名空间中几个类的关系:
System.Reflection命名空间
(1) AppDomain:应用程序域,可以将其理解为一组程序集的逻辑容器
(2)Assembly:程序集类
(3)Module:模块类
(4)Type:使用反射得到类型信息的最核心的类
他们之间是一种从属关系,也就是说,一个AppDomain可以包含N个Assembly,一个Assembly可以包含N个Module,而一个Module可以包含N个Type.
Assembly类加载程序集有三种方法,Load(),LoadFrom(),LoadFile();
Load():该方法会有多个重载版本,其中一个就是提供程序集的详细信息,即程序集的标识,包括程序集的名称,版本,区域信息,公有密钥标记,全部都是以一个字符串的形式提供,例如:"MyAssembly,Version=1.0.0.0,culture=zh-CN,PublicKeyToken=47887f89771bc57f”.

那么,使用Assembly.Load加载程序集的顺序是怎样的呢?首先它会去全局程序集缓存查找,然后到应用程序的根目录查找,最后会到应用程序的私有路径查找。

当然,如果你使用的是弱命名程序集,也即只给出程序集的名称,那么这个时候,CLR将不会在程序集上应用任何安全或者部署策略,而且Load也不会到全局缓存程序集中查找程序集。
测试加载弱命名程序集的例子如下:

(1) 新建一个控制台应用程序的工程,同时勾选创建解决方案

(2) 在解决方案中新建一个类库的项目,随便写一个类和一个方法

(3) 在控制台项目中,首先不添加引用,直接在Main方法中添加如下代码:

Assembly assembly = Assembly.Load(“MyAssembly”);

if (assembly != null)

{ Console.WriteLine(“加载成功”); }

执行程序,会抛出异常,说找不到该程序集。什么原因呢?因为我们使用的是弱命名程序集,Load方法不会去全局程序集缓存中查找,而该应用程序目录下又没有该程序集,所以程序找不到。这个时候,我们把程序稍微改一下,不用添加代码,只需添加对MyAssembly的引用,重新运行程序,加载成功了。
接下来,我们就要看看Load怎么加载强命名程序集了,这个步骤稍微有些复杂。还是刚才的项目,找到MyAssembly.dll程序集所在的目录,一般在bin"Debug目录下

(1)生成密钥对文件 sn –k MyAssemblyKey.keys

你也可以自己随便起一个密钥对文件名

(2)生成公钥文件

sn –p MyAssemblyKey.keys MyAssemblyPublicKey.PublicKey

注:查看公钥命令:sn –tp MyAssemblyPublicKey.PublicKey

(3)创建强命名程序集。

很简单,只需要在声明命名空间的那句代码上加上如下特性:

[assembly:AssemblyKeyFileAttribute(@”D:"Test"MyAssemblyKey.keys”)]

(4) 编译项目

(5) 将程序集添加到程序集全局缓存

gacutil –i MyAssembly.dll

这个时候,转到加载程序集的项目中,将Load方法中的参数改为”程序集名,Version=版本,culture=区域信息,PublicKeyToken=公钥“,然后再去掉对程序集的引用,我们会发现,程序运行成功。表明Load到全局缓存区查找到了该程序集。

使用Load方法加载程序集,特别是强命名程序集,能在程序集上应用安全和部署策略,推荐使用该方法动态加载程序集,至于LoadFrom和LoadWithPartialName。
LoadFrom()方法可以从指定文件中加载程序集,通过查找程序集的AssemblyRef元数据表,得知所有引用和需要的程序集,然后在内部调用Load()方法进行加载。例如:Assembly.LoadFrom(@“C:\ABC\Test.dll”);

LoadFrom()首先会打开程序集文件,通过GetAssemblyName方法得到程序集名称,然后关闭文件,最后将得到的AssemblyName对象传入Load()方法中,随后,Load()方法会再次打开这个文件进行加载。所以,LoadFrom()加载一个程序集时,会多次打开文件,造成了效率低下的现象(与Load相比).由于内部调用了Load(),所以LoadFrom()方法还是会应用版本绑定重定向策略,也会在GAC和各个指定位置中进行查找.如果目标程序集已经加载过,LoadFrom()不会重新进行加载.
LoadFile()从一个指定文件中加载程序集,它和LoadFrom()的不同之处在于LoadFile()不会加载目标程序集所引用和依赖的其他程序集。您需要自己控制并显示加载所有依赖的程序集.

反射的成员

MemberInfo-成员
ConstructorInfo-结构
FieldInfo-字段
MethodInfo-字段
PropertyInfo-属性
EventInfo-事件
//获取类型
Type[] tyleList=assembly.GetTypes();
//获取类型的结构信息
ConstructorInfo[] constructs=type.GetConstructors();
//获得类型的字段信息
FieldInfo[] fields=type.GetFields();
//获取方法信息
MethodInfo[] methods=type.GetMethods();
//获取方法的参数信息
ParameterInfo[] parameters=method.GetParameters();
//获取方法的返回值信息
ParameterInfo reparam=method.ReturnParameter;
//获取属性的信息
PropertyInfo[] peopertys=type.GetProperties();
//获取事件信息
EventInfo[] events=type.GetEvents();

动态创建对象

Assemble对象的CreateInstance方法
Activator.CreateInstance方法
Type对象的InvokeMember方法
//创建无参
Assembly.Load(“程序集”).CreateInstance(“命名空间.类名”)
创建有参
Object[] parameters = new Object[1];
parameters[0] = 10000;
Assembly.Load(assemblyName).CreateInstance(className, false, BindingFlags.Default, null, parameters, null, null);
// 创建无参数实例
System.Runtime.Remoting.ObjectHandle obj = Activator.CreateInstance(assemblyName, className);
IDAL.IMember member = (IDAL.IMember)obj.Unwrap();
Response.Write(“创建无参数实例:” + member.ID + “
”);
// 创建有参数实例
Object[] parameters = new Object[1];
parameters[0] = 10000;
System.Runtime.Remoting.ObjectHandle obj1 = Activator.CreateInstance(assemblyName, className, false, BindingFlags.CreateInstance, null, parameters, null, null, null);
// 使用Type的InvokeMember方法来取得对象的实例
private void Type_InvokeMember()
{
string assemblyName = “SqlModel”;
string className = assemblyName + “.Member”;
Assembly assem = Assembly.Load(assemblyName);
Type type = assem.GetType(className); // 註意这里如果使用Type.GetType来取得Type的话,那麼assemblyName指定的类一定要是强命名的
// 创建无参数实例
IDAL.IMember member = (IDAL.IMember)type.InvokeMember(className, BindingFlags.CreateInstance, null, null, null);
Response.Write(“创建无参数实例:” + member.ID + “
”);
// 创建有参数实例
Object[] parameters = new Object[1];
parameters[0] = 10000;
IDAL.IMember member1 = (IDAL.IMember)type.InvokeMember(className, BindingFlags.CreateInstance, null, null, parameters);
Response.Write(“创建有参数实例:” + member1.ID + “
”);
}
七、动态调用对象方法

Type对象的 InvokeMember方法
MethodInfo对象的Invoke方法
// Type对象的 InvokeMember方法来动态调用方法
private void InvokeMember()
{
string assemblyName = “SqlModel”;
string className = assemblyName + “.Member”;
string methodName = String.Empty;
string result = String.Empty;
Assembly assem = Assembly.Load(assemblyName);
Object obj = assem.CreateInstance(className);
Type type = assem.GetType(className); // 註意这里如果使用Type.GetType来取得Type的话,那麼assemblyName指定的类一定要是强命名的
// 动态调用无参数的方法
methodName = “GetName”;
result = (string)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, null);
Response.Write(methodName + “方法的返回值:” + result + “
”);
// 动态调用有参数的方法
methodName = “Update”;
Object[] methodParams = new Object[1];
methodParams[0] = DateTime.Now;
result = (string)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, methodParams);
Response.Write(methodName + “方法的返回值:” + result + “
”);
// 动态调用参数构架函数的带有参数的方法
Object[] parameters = new Object[1];
parameters[0] = 10000;
obj = assem.CreateInstance(className,false,BindingFlags.CreateInstance, null, parameters, null, null);
result = (string)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, methodParams);
Response.Write(methodName + “方法的返回值:” + result + “
”);
}

// MethodInfo对象的Invoke方法来动态调用方法

private void MethodInfo_Invoke()
{
string assemblyName = “SqlModel”;
string className = assemblyName + “.Member”;
string methodName = String.Empty;
string result = String.Empty;

Assembly assem = Assembly.Load(assemblyName);
Object obj = assem.CreateInstance(className);
Type type = assem.GetType(className);   // 註意这里如果使用Type.GetType来取得Type的话,那麼assemblyName指定的类一定要是强命名的
// 动态调用无参数的方法
methodName = "GetName";
MethodInfo methodInfo = type.GetMethod(methodName);
result = (string)methodInfo.Invoke(obj, null);
Response.Write(methodName + "方法的返回值:" + result + "<br/>");
// 动态调用有参数的方法
methodName = "Update";
Object[] methodParams = new Object[1];
methodParams[0] = DateTime.Now;
MethodInfo method = type.GetMethod(methodName);
result = (string)method.Invoke(obj, methodParams);
Response.Write(methodName + "方法的返回值:" + result + "<br/>");

}


以上所使用的SqlModel.Member為:
新建一个SqlModel类库,在其下建立一个Member的类

namespace SqlModel
{
public class Member : IDAL.IMember
{
private int _id = 100;
public int ID
{
get { return _id; }
set { _id = value; }
}
private string _name = “limin”;
public string Name
{
get { return _name; }
set { _name = value; }
}

    public Member() { }
    public Member(int id)
    {
        _id = id;
    }

    private void Init()
    { }

    public string GetName()
    {
        return _name;
    }
    public string Update (DateTime cdate)
    {
        return "{" + String.Format("ID:{0},Name:{1},CreateDate:{2}",_id,_name,cdate) + "}";
    }
}

}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值