这一章节,我们来探讨一下反射。对于初学者呢,反射是一件很恶心的事。那么在研究反射之前,我们来了解一下什么是反射。反射就是一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。
程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
举一个通俗的例子:恐龙已经灭绝很多年了,这是众所周知的问题,但是我们如果拿到一个恐龙的DNA是不是就可以通过一个技术手段得到一个人类创造的恐龙呢?其实也可以简单的理解成克隆羊多利与成熟体细胞的关系。多利并不是受精卵发育而来。
而在C#中反射就可以理解成这样一种机制。它可以让我们获取正在运行的对象,同时改变他的状态或行为。
它具有一下的用途:
1.使用 Assembly 定义和加载程序集,加载在程序集清单中列出的模块,以及从此程序集中查找类型并创建该类型的实例。
2.使用 Module 了解如下的类似信息:包含模块的程序集以及模块中的类等。您还可以获取在模块上定义的所有全局方法或其他特定的非全局方法。
3.使用 ConstructorInfo 了解如下的类似信息:构造函数的名称、参数、访问修饰符(如 public 或 private)和实现详细信息(如 abstract 或 virtual)等。
4.使用 Type 的 GetConstructors 或 GetConstructor 方法来调用特定的构造函数。
5.使用 MethodInfo 来了解如下的类似信息:方法的名称、返回类型、参数、访问修饰符(如 public 或 private)和实现详细信 息(如 abstract 或 virtual)等。使用 Type 的 GetMethods 或 GetMethod 方法来调用特定的方法。
6.使用 FieldInfo 来了解如下的类似信息:字段的名称、访问修饰符(如 public 或 private)和实现详细信息(如 static)等;并获取或设置字段值。
7.使用 EventInfo 来了解如下的类似信息:事件的名称、事件处理程序数据类型、自定义属性、声明类型和反射类型等;并添加或移除事件处理程序。
8.使用 PropertyInfo 来了解如下的类似信息:属性的名称、数据类型、声明类型、反射类型和只读或可写状态等;并获取或设置属性值。
9.使用 ParameterInfo 来了解如下的类似信息:参数的名称、数据类型、参数是输入参数还是输出参数,以及参数在方法签名中的位置等。
我们通过代码来看一下上面的用途同时来体会一下反射的优点和缺点:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace 反射1
{
class Program
{
//D:\Qikuedu\1609\MyCode\反射\GenLibTest\bin\Debug\GenLibTest.dll
static void Main(string[] args)
{
#region
Type type = typeof(Animal);
//获取类型名
Console.WriteLine(type.Name);
//命名空间名
Console.WriteLine(type.Namespace);
//程序集名
Console.WriteLine(type.Assembly);
//基类名字
Console.WriteLine(type.BaseType);
//是否是一个类
Console.WriteLine("是否类:" + type.IsClass);
Console.WriteLine("类的公共成员:");
MemberInfo[] memberInfos = type.GetMembers();
foreach (var item in memberInfos)
{
Console.WriteLine(string.Format("{0}:{1}", item.MemberType, item));
}
#endregion
#region
Assembly assem = Assembly.GetExecutingAssembly();
Console.WriteLine("程序集全名:" + assem.FullName);
Console.WriteLine("程序集的版本:" + assem.GetName().Version);
Console.WriteLine("程序集初始化位置:" + assem.CodeBase);
Console.WriteLine("程序集位置:" + assem.Location);
Console.WriteLine("程序集入口: " + assem.EntryPoint);
Type[] types = assem.GetTypes();
Console.WriteLine("程序集下包含的类型:");
foreach (var item in types)
{
Console.WriteLine("类:" + item.Name);
}
#endregion
#region
Assembly assembly = Assembly.LoadFile(@"D:\Qikuedu\1609\MyCode\反射\GenLibTest\bin\Debug\GenLibTest.dll");
object person = assembly.CreateInstance("GenLibTest.Person");
Type[] types1 = assembly.GetTypes();
foreach (var item in types1)
{
Console.WriteLine(item.FullName);
}
////这里是始终得不到Person这个对象的。
Assembly currentAssem = Assembly.GetExecutingAssembly();
//object myClass = currentAssem.CreateInstance("反射1.Animal");
Animal animal = currentAssem.CreateInstance("反射1.Animal") as Animal;
//animal.Age = 10;
Console.WriteLine(animal);
#endregion
//Type t = Type.GetType("反射1.Animal");
//object tt = Activator.CreateInstance(t,new object[]{ "tom"});
//Console.WriteLine(tt);
Assembly assembly1 = Assembly.LoadFile(@"D:\Qikuedu\1609\MyCode\反射\GenLibTest\bin\Debug\GenLibTest.dll");
object person1 = assembly1.CreateInstance("GenLibTest.Person");
//Type personType = Type.GetType("GenLibTest.Person");
Type personType = person1.GetType();
FieldInfo fi = personType.GetField("name");
fi.SetValue(person1, "Jack");
Console.WriteLine(fi.GetValue(person1));
PropertyInfo pi = personType.GetProperty("Age");
pi.SetValue(person1, 123);
Console.WriteLine(pi.GetValue(person1));
MethodInfo mi = personType.GetMethod("Show");
mi.Invoke(person1,new object[] { "我是中国人"});
}
}
class Animal
{
private string _name;
private int _age;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
public int Age
{
get
{
return _age;
}
set
{
_age = value;
}
}
//如果在这里没有写这个无参的构造函数的话,那么就会报错,这就是为什么告诉你们为什么要在写类的时候写无参的构造
public Animal()
{
}
public Animal(string name,int age)
{
Name = name;
Age = age;
}
public Animal(string _name)
{
this._name = _name;
}
}
}
程序中的dll库是自己生成的,生成的方式:
如下:
在新建项目中添加类库
相关代码写完后点击上方菜单栏中的“生成”即可
反射还可以让我们通过写配置文件,以便于我们更好的使用程序:
首先我们在自己的硬盘的某个目录下面添加MyConfig.txt配置文件。里面的内容
#Reflection3.TomInput
Reflection3.JackInput
#Reflection3.MaryInput
而我们的程序中写的代码如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Reflection3
{
public interface IMyInput
{
int Cal(int x, int y);
}
class TomInput : IMyInput
{
public int Cal(int x, int y)
{
return x + y;
}
}
class JackInput : IMyInput
{
public int Cal(int x, int y)
{
return x - y;
}
}
class MaryInput : IMyInput
{
public int Cal(int x, int y)
{
return x * y;
}
}
class Program
{
static void Main(string[] args)
{
//MaryInput mary = new MaryInput();
//Console.WriteLine(mary.Cal(1,2));
Assembly ass = Assembly.GetExecutingAssembly();
List<string> list = new List<string>();
StreamReader sr = new StreamReader(@"D:\Qikuedu\1609\MyCode\MyConfig.txt", Encoding.ASCII);
string str = string.Empty;
while ((str = sr.ReadLine()) != null)
{
list.Add(str);
Console.WriteLine(str);
}
foreach (string s in list)
{
if (!s.StartsWith("#"))
{
str = s;
}
}
IMyInput input = ass.CreateInstance(str) as IMyInput;
Console.WriteLine(input.Cal(3, 2));
}
}
}
这样我们就可通过在配置文件中增删#号从而改变程序的运行结果了。这样使用反射是不是很爽?
优点:
1、反射提高了程序的灵活性和扩展性。
2、降低耦合性,提高自适应能力。
3、它允许程序创建和控制任何类的对象,无需提前硬编码目标类。
缺点:
1、性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
2、使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。