C#反射及应用

时间:2022-01-30 19:18:23

反射就是动态加载某一个程序集,获取程序集中类型然后调用其中的方法或者属性等。我们通常不太注意经常使用反射的地方,如vs编译器,我们在打一个类的时候,里面的方法、属性、字段等信息都会出现智能提示这个时候就是使用反射实现的。还有我们使用的reflactor等反编译器等也是这样的;xml、js、二进制序列化等也是使用反射,否则如何知道需要序列化的字段有哪些呢;访问一个程序集中的私有方法;各种框架中使用反射,即修改配置文件就会得到相应的改变使用的都是反射。反射的另一个作用就是我们可以在做一个可拓展的程序,用插件来实现程序功能拓展,我们可以使用反射、接口,约定一个方法只有实现了特定接口的类,才可以作为插件来使用。如记事本,我们可以给它写插件来更改字体颜色、字体大小、大小写转换等。反射不仅仅可以调用某一个程序集中的public方法或者属性,private也是可以进行调用的,但是这种情况下最好别调用其他程序中的private方法,因为private方法以后有可能会更改,不过某一些特殊情况下为了达到某一些特殊目的,不得不调用的时候也得调用。反射效率并不高,所以一切视具体情况而定。反射中有两个比较重要的东西一个就是Assembly一个是Type。其中Assembly就是动态来加载程序集的,Type是取得其中程序集中的类型。如果一个类型我们已经知道类名,我们可以使用typeof(类名)来获取type,如果只有对象我们可以使用对象.GetType()来获取type。

常用代码如下,eg:

//1>动态加载程序集
Assembly asm= Assembly.LoadFile(@"c:\aa.dll");


//2>获取类型
Type[] types=asm.GetTypes();//获取所有类型名
Type type= asm.GetType("完全限定名称即命名空间+类名");//获取特定类型名
Type[] types= asm.GetExportedTypes();//获取所有Public类型

 获取某一个类型方法之后可以使用invoke来进行调用。

 

下面讲解一个有插件的记事本,代码如下:

 

主窗体(有一个MenuStrip、一个TextBox)界面参照Windows自带的记事本

private void Form1_Load(object sender, EventArgs e)
{
    string[] directoryInfos = Directory.GetFiles(Path.Combine(Application.StartupPath.ToString(), "Plugins"),
        "*.dll");
    foreach (var directoryInfo in directoryInfos)
    {
        Assembly assembly = Assembly.LoadFile(directoryInfo);
        Type[] types = assembly.GetTypes();//获取所有类型,包括public、internal修饰符的        //Type[] types = assembly.GetExportedTypes();//只能获取public类型的,internal修饰符修饰的类型不可获取。
        Type myInterfaceType = typeof(IMyInterface);
        foreach (var type in types)
        {
            if (!type.IsAbstract && myInterfaceType.IsAssignableFrom(type))//判断必须不是抽象类,并且是实现了IMyInterface接口的类。
            {
                ToolStripItem tsi = new ToolStripButton();
                IMyInterface obj = (IMyInterface)Activator.CreateInstance(type);
                tsi = toolStripMenuItem1.DropDownItems.Add(obj.Name);
                tsi.Tag = obj;
                tsi.Click += tsi_Click; ;
            }
        }
    }
}

void tsi_Click(object sender, EventArgs e)
{
    ToolStripItem obj = sender as ToolStripItem;
    if (obj != null)
    {
        IMyInterface myInterface = obj.Tag as IMyInterface;
        myInterface.Run(textBox1);//约定必须是Run方法
    }
}

 

接口

public interface IMyInterface
{
    string Name { get; }
    void Run(TextBox str);
}

 

插件1:实现了大写转变

public class Class1:IMyInterface
{
    public string Name
    {
        get { return "大写转化"; }
    }
    public void Run(System.Windows.Forms.TextBox str)
    {
        str.Text = str.Text.ToUpper();
    }
}

 

插件2:实现了字体的转变和字体大小的转变

public class Class1 : IMyInterface
{

    public string Name
    {
        get { return "字体"; }
    }

    public void Run(TextBox str)
    {
        str.Font=new Font("华文行楷",70);
    }
}

最后实现的效果就是我们最后把两个插件类库生成的dll文件拷贝到主程序的Plugins文件夹下,然后重启主程序就可以实现靠插件实现拓展功能。

 


利用反射调用类中的私有方法,代码如下,eg:

主方法

 1 class Test
 2 {
 3     private static Type type = typeof(Class1);
 4 
 5     static void Main()
 6     {
 7         //object obj = Activator.CreateInstance(type);//默认调用的是无参构造函数
 8         object obj = Activator.CreateInstance(type,100,160);//调用有两个int类型参数的构造函数
 9         //MethodInfo[] methodInfos = type.GetMethods();//只可以获取public方法
10         MethodInfo[] methodInfos = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);//可以获取private方法
11         foreach (var methodInfo in methodInfos)
12         {
13             if (methodInfo.Name == "ReturnValue")
14             {
15                 Console.WriteLine(methodInfo.Invoke(obj, null));
16             }
17         }
18         Console.ReadLine();
19     }
20 }

包含私有方法的类

public class Class1
{
    private readonly int _i;
    private readonly int _j;
    public Class1(int i, int j)
    {
        this._i = i;
        this._j = j;
    }
    public void GetValue()
    {
        Console.WriteLine("getValue method");
    }

    private string ReturnValue()
    {          
        return (_i + _j).ToString();
    }
}

结果为:260

总结:我们在调用构造函数的时候可以使用Activator来创建对象,同时还可以使用GetConstructor来创建对象。

上面的  object obj = Activator.CreateInstance(type,100,160); 可以换成如下代码,eg:

//ConstructorInfo [] constructorInfo = type.GetConstructors();//可以获取所有的构造函数
ConstructorInfo constructorInfo = type.GetConstructor(new Type[]{typeof(int),typeof(int)});//可以获取特定某一个的构造函数,其中的new Type[]数组是一个参数列表中的参数类型,要跟参数列表中的参数数量及类型相对应。
object obj = constructorInfo.Invoke(new object[] { 100, 160 });

需要区分的有一个地方就是获取所有类行还有获取所有方法,获取所有类型,代码如下,eg:

Type[] types = assembly.GetTypes();//获取所有类型,public和internal都可以获取。

//Type[] types = assembly.GetExportedTypes();//获取所有可以导出的类型即public类型的,不包括internal类型的
 

下面说一下获取方法,代码如下:

MethodInfo[] methodInfos = type.GetMethods();//只可以获取public方法
MethodInfo[] methodInfos = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance);//可以获取private方法

当我们需要判断当前类型是否是抽象的,我们可以使用type类型的IsAbstract,判断当前类型是否是抽象类型。(编译过后接口其实也是一个抽象方法)

当我们需要判断一个类型是否是一个类的子类的时候,我们可以使用type类型的IsSubClassOf,这个方法只是用来判断类的不能用来判断接口。

当我们需要判断一个对象是不是一个类型的对象的时候,我们可以使用IsTypeOfInstanceOfType(类、接口都适用)

当我们需要判断一个类型是否可以接受另一个类型的值的时候,我们需要使用IsAssignFrom