这次实验是单纯的对C#反射特性的学习,没什么特别的。
前期准备
实验要求
3.2 构建一个components.txt文件,每行代表一个类(比如形状类Shape)的名字(这些类可以来自系统库,或者自己构造);程序逐行扫描该文件,构建对应的类的对象。要求:
1)并把这些对象放到数组中;
- 列出每个类的字段、方法;
3)让用户选择,使得用户可以调用所需要的方法(操作)
4)系统随机选择对象(比如形状),随机的执行其操作。从而看系统演化。可能的话,进行界面展示
前置知识
实验要求中需要我们通过类名字实例化对象,且列出其字段与方法,并尝试调用方法。以上这些操作都指向了C#中很重要的一个知识点——反射。
反射提供描述程序集、模块和类型的对象(Type 类型)。 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问器字段和属性。 如果代码中使用了特性,可以利用反射来访问它们。
如果对反射还不太理解,建议参阅C#官方文档。
而从文件中读取的操作则需要System.IO中File类的操作。
对于调用形状库,则需要使用System.Reflection中的Assembly.LoadFrom方法。
有了以上基础知识的积累,就可以开始编写程序了。
实验过程
构建形状库
首先创建一个cs文件,写入形状类代码:
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
| using System;
namespace ShapeRefl { public class Shape { public const double PI = Math.PI; protected double x, y; public Shape() { } public Shape(double x,double y) { this.x = x; this.y = y; } public virtual double GetArea() { return 0; } public virtual double GetPerimeter() { return 0; } }
public class Square : Shape { public Square(double x,double y) : base(x, 0) { } public override double GetArea() { return x * x; } public override double GetPerimeter() { return 4 * x; } } public class Rectangle : Shape { public Rectangle(double x,double y) : base(x, y) { } public override double GetArea() { return x * y; } public override double GetPerimeter() { return 2 * (x + y); } } public class Triangle : Shape { public Triangle(double x,double y) : base(x, y) { } public override double GetArea() { return 0.5 * x * y; } public override double GetPerimeter() { return Math.Sqrt(x * x * 0.25 + y * y) * 2 + x; } } public class Circle : Shape { public Circle(double x,double y) : base(x, 0) { } public override double GetArea() { return PI * x * x; } public override double GetPerimeter() { return 2 * PI * x; } } public class Ellipse : Shape { public Ellipse(double x,double y) : base(x, y) { } public override double GetArea() { return PI * x * y; } public override double GetPerimeter() { return 2 * PI * y + (x - y) * 4; } } }
|
但设计完了这些类,却不能直接被另一个文件所引用。我们需要将其打包成一个dll库文件。
打开解决方案资源管理器 ,右键项目 ,选择属性 ,打开项目的属性设置界面。
将项目的输出类型设为类库 。
最后回到解决方案资源管理器 ,右键解决方案 ,点击重新生成解决方案 。
稍作等待,就能在下方给出的地址中获得生成的dll类库文件了。
先将这个类库文件存下来,取个好记的名字,比如我这里改成了Shape.dll,后续在代码中会用到。
主体代码
调用类库
新创建一个项目,作为我们的代码主体。先随便写点东西生成一次解决方案。此时我们可以在Debug文件夹下找到项目的exe文件,记住这个目录,并将之前那个dll文件移动到此处,同时创建components.txt文件,作为读取文件。
此时回到代码,开始程序的编写:
1 2
| Assembly assembly = Assembly.LoadFrom("Shape.dll"); string fileName = "components.txt";
|
创建一个字符串变量fileName作为读取文件路径,方便后续调用。再创建一个Assembly变量加载之前编写的类库。
同时在Main函数外创建一个全局变量,作为实例化的类对象的数组:
1
| static ArrayList clsList = new ArrayList();
|
读取文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| static void ReadFromFile(string fileName,Assembly assembly) { FileStream file = File.OpenRead(fileName); StreamReader stream = new StreamReader(file, System.Text.Encoding.Default); file.Seek(0, SeekOrigin.Begin); string nspName = "ShapeRefl."; string clsName = ""; object[] ars = { 3, 5 };
while (stream.Peek() > -1) { clsName = stream.ReadLine(); object obj = assembly.CreateInstance(nspName + clsName, true, BindingFlags.Default, null, ars, null, null); clsList.Add(obj); } }
|
首先创建一个读取文件流与一个文件流指针,并从文件头开始读取:
1 2 3
| FileStream file = File.OpenRead(fileName); StreamReader stream = new StreamReader(file, System.Text.Encoding.Default); file.Seek(0, SeekOrigin.Begin);
|
接下来开始逐行读入文件名,并由读入的文件名实例化对象以及将其加入数组:
1
| object obj = assembly.CreateInstance(nspName + clsName, true, BindingFlags.Default, null, ars, null, null);
|
这部分的核心是实例化对象的函数CreateInstance()。对于需要写入参数的函数要传入多个参数。大部分参数不用修改,只需特别关注第一与第五个参数:第一个参数为完整的类名,即需要以“命名空间.类名”的形式写入,由于我们的类都在同一个命名空间中,所以无需额外写入,只需修改类名即可。而第五个参数为传入构造函数的参数,用一个object[]来储存,如用ars={3,5}来实例化Square对象,其实就相当于new Square(3,5)。
列出信息
这部分函数可谓展示了反射的大部分内容,是该程序的核心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| static void ShowAll() { for(int i=0;i<clsList.Count;i++) { Type type = clsList[i].GetType(); if (type != null) Console.WriteLine("- {0}: <{1}>", i+1, type.FullName); MethodInfo[] methods = type.GetMethods(); Console.WriteLine("\t方法({0})", methods.Length); foreach (MethodInfo method in methods) Console.WriteLine("\t|- {0}", method.Name); FieldInfo[] fields = type.GetFields(); Console.WriteLine("\t字段({0})", fields.Length); foreach (FieldInfo field in fields) Console.WriteLine("\t|- {0}", field.Name); } }
|
GetType()函数可以获得当前类的类型,将其存在type变量中。对该变量,可以进行FullName,GetMethods()与GetFields()等操作,分别为获得其完整的名字、获得其所有方法以及获得其所有字段。使用这几个方法,即可完成对其方法与字段的打印。
选择调用
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
| static void UserChoose() { for (int i = 0; i < clsList.Count; i++) { Type type = clsList[i].GetType(); if (type != null) Console.WriteLine("- {0}: <{1}>", i + 1, type.FullName); } Console.WriteLine("请选择要访问的类(1~{0})", clsList.Count); int clsChoice; clsChoice = int.Parse(Console.ReadLine())-1; if (clsChoice >= 0 && clsChoice < clsList.Count) { Type type = clsList[clsChoice].GetType(); MethodInfo[] methods = type.GetMethods(); Console.WriteLine("方法({0})", methods.Length); for (int i = 0; i < methods.Length; i++) Console.WriteLine("|{0}. {1}", i + 1, methods[i].Name); Console.WriteLine("请选择要访问的方法(1~{0})", methods.Length); int mtdChoice; mtdChoice= int.Parse(Console.ReadLine())-1; if (mtdChoice >= 0 && mtdChoice < methods.Length && mtdChoice != 4) Console.WriteLine(methods[mtdChoice].Name + ":" + methods[mtdChoice].Invoke(clsList[clsChoice], null)); } }
|
已经实现了以上列出信息的部分,选择调用的部分就不再复杂,无非是做点基础的语法调整。但这里需要额外注意一下:第4号方法(从0开始计数)为Equal方法,为二元方法,无法被直接调用打印,所以需要跳过,否则程序会崩溃。
随机调用
该部分的代码实现也与上述过程如出一辙:
1 2 3 4 5 6 7 8 9 10 11 12
| static void SystemRand() { Random random = new Random((int)DateTime.Now.Ticks); int clsChoice = random.Next(0, clsList.Count - 1); Type type = clsList[clsChoice].GetType(); Console.WriteLine(type.Name); MethodInfo[] methods = type.GetMethods(); int mtdChoice = random.Next(0, 5); while (mtdChoice == 4) mtdChoice = random.Next(0, 5); Console.WriteLine(methods[mtdChoice].Name + ":" + methods[mtdChoice].Invoke(clsList[clsChoice], null)); }
|
但需要引入一个随机数生成器Random,其构造函数可以引入一个参数作为种子 ,一般情况下为了接近真正的随机,我们选用当前时间tick数(DateTime.Now.Ticks作为种子。有了这个生成器,我们就可以用Next()方法获取下一个随机数了。值得注意的是,这里也需要避开Equal方法的直接调用。
结尾
完成了以上函数,我们就只需要稍微润色一下Main函数,用文字实现简单的操作交互即可:
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
| static void Main(string[] args) { Assembly assembly = Assembly.LoadFrom("Shape.dll"); string fileName = "components.txt"; ReadFromFile(fileName, assembly); string action = "9"; while (action != "0") { Console.Clear(); switch (action) { case "1": ShowAll(); break; case "2": UserChoose(); break; case "3": SystemRand(); break; default:break; } Console.WriteLine("请输入操作代码:"); Console.WriteLine("1.列出所有类的字段与方法"); Console.WriteLine("2.由用户选择操作"); Console.WriteLine("3.系统随机选择操作"); Console.WriteLine("0.退出程序"); action = Console.ReadLine(); } }
|
完整代码
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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| using System; using System.IO; using System.Reflection; using System.Collections;
namespace testConsole { class Program { static ArrayList clsList = new ArrayList(); static void Main(string[] args) { Assembly assembly = Assembly.LoadFrom("Shape.dll"); string fileName = "components.txt"; ReadFromFile(fileName, assembly); string action = "9"; while (action != "0") { Console.Clear(); switch (action) { case "1": ShowAll(); break; case "2": UserChoose(); break; case "3": SystemRand(); break; default:break; } Console.WriteLine("请输入操作代码:"); Console.WriteLine("1.列出所有类的字段与方法"); Console.WriteLine("2.由用户选择操作"); Console.WriteLine("3.系统随机选择操作"); Console.WriteLine("0.退出程序"); action = Console.ReadLine(); } } static void ReadFromFile(string fileName,Assembly assembly) { FileStream file = File.OpenRead(fileName); StreamReader stream = new StreamReader(file, System.Text.Encoding.Default); file.Seek(0, SeekOrigin.Begin); string nspName = "ShapeRefl."; string clsName = ""; object[] ars = { 3, 5 };
while (stream.Peek() > -1) { clsName = stream.ReadLine(); object obj = assembly.CreateInstance(nspName + clsName, true, BindingFlags.Default, null, ars, null, null); clsList.Add(obj); } } static void ShowAll() { for(int i=0;i<clsList.Count;i++) { Type type = clsList[i].GetType(); if (type != null) Console.WriteLine("- {0}: <{1}>", i+1, type.FullName); MethodInfo[] methods = type.GetMethods(); Console.WriteLine("\t方法({0})", methods.Length); foreach (MethodInfo method in methods) Console.WriteLine("\t|- {0}", method.Name); FieldInfo[] fields = type.GetFields(); Console.WriteLine("\t字段({0})", fields.Length); foreach (FieldInfo field in fields) Console.WriteLine("\t|- {0}", field.Name); } } static void UserChoose() { for (int i = 0; i < clsList.Count; i++) { Type type = clsList[i].GetType(); if (type != null) Console.WriteLine("- {0}: <{1}>", i + 1, type.FullName); } Console.WriteLine("请选择要访问的类(1~{0})", clsList.Count); int clsChoice; clsChoice = int.Parse(Console.ReadLine())-1; if (clsChoice >= 0 && clsChoice < clsList.Count) { Type type = clsList[clsChoice].GetType(); MethodInfo[] methods = type.GetMethods(); Console.WriteLine("方法({0})", methods.Length); for (int i = 0; i < methods.Length; i++) Console.WriteLine("|{0}. {1}", i + 1, methods[i].Name); Console.WriteLine("请选择要访问的方法(1~{0})", methods.Length); int mtdChoice; mtdChoice= int.Parse(Console.ReadLine())-1; if (mtdChoice >= 0 && mtdChoice < methods.Length && mtdChoice != 4) Console.WriteLine(methods[mtdChoice].Name + ":" + methods[mtdChoice].Invoke(clsList[clsChoice], null)); } } static void SystemRand() { Random random = new Random((int)DateTime.Now.Ticks); int clsChoice = random.Next(0, clsList.Count - 1); Type type = clsList[clsChoice].GetType(); Console.WriteLine(type.Name); MethodInfo[] methods = type.GetMethods(); int mtdChoice = random.Next(0, 5); while (mtdChoice == 4) mtdChoice = random.Next(0, 5); Console.WriteLine(methods[mtdChoice].Name + ":" + methods[mtdChoice].Invoke(clsList[clsChoice], null)); } } }
|