序列化和反射

序列化

将对象的状态存储到特定存储介质中的过程,可以说是将对象状态转换为可保持或传输的格式的过程,在序列化过程中,会将对象的公有成员、私有成员包括类名,都转换成数据流的形式,存储到存储介质中,这里说的存储介质通常指的是文件。

把数据转换成文件,保存到硬盘

引入using System.Runtime.Serialization.Formatters.Binary;

在类的头部添加一个标记[Serializable]。用于标记该类是否可序列化。它的本身称为可序列化特性。

所谓特性,就是为目标元素(可以是数据集、模块、类、属性、方法甚至函数参数等)加入附加信息,类似于注释

[Serializable]
    class Profile
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            List<Profile> profiles = new List<Profile>();
            profiles.Add(new Profile() { Id = 1, Name = "12" });
            profiles.Add(new Profile() { Id = 2, Name = "123" });
            profiles.Add(new Profile() { Id = 3, Name = "1234" });
            profiles.Add(new Profile() { Id = 4, Name = "12345" });
            profiles.Add(new Profile() { Id = 5, Name = "123456" });


            FileStream fileStream = null;
            //定义一个文件流
            fileStream = new FileStream("profile.bin", FileMode.Create);
            //二进制方式
            BinaryFormatter df = new BinaryFormatter();
            //序列化保存配置文件对象Profile
            df.Serialize(fileStream, profiles);

        }
    }

序列化需要通过文件流来保存到文件,所以先定义一个文件流,BinaryFormatter是一个二进制格式化器,其具有一个非常重要的Serialize()方法

Serialize()方法
public void Serialize(Stream serializationStream, object graph);
  • serializationStream是指定序列化过程的文件流。通道
  • graph是要保存的对象

注意:如果需要序列化某个特定对象,那么它的各个成员对象也必须是可序列化的

反序列化

将文件转换问数据到内存

使用的方法是Deserialize()

public object Deserialize(Stream serializationStream);

注意,Deserialize()方法将存储介质的数据文件流转换为Object,还需要进一步转换为相应的对象类型。

程序集

编译好的.exe文件,称为程序集。它是.NET框架应用程序的生成块,包含编译好的代码逻辑单元。

程序集包含程序集清单、类型元数据、IL代码、资源

程序集的结构
程序集清单,每个程序集都包含描述该程序集中各元素彼此如何关联的数据集合。程序集清单包含这些程序集的元数据。程序集清单包含指定该程序集的版本要求和安全标识所需的所有元数据。

信息

说明

程序集名称

指定程序集名称的文本字符串

版本号

主版本号和次版本号,以及修订号和内容版本号

区域性

有关该程序集支持的区域性或语言的信息

强名称信息

如果已经为程序集提供一个强名称,则为来自开发者的公钥

程序集中所有文件的列表

构成该程序集的文件

类型引用信息

控制对该程序集的类型和资源的引用如何映射到包含其声明和实现的文件中

有关被引用程序集的信息

该信息用于从程序集导出的类型

  • 主要功能
  • 列举构成该程序集的文件
  • 控制对该程序集的类型和资源的引用如何映射到包含其声明和实现的文件中
  • 列举该程序集所依赖的其他程序集
  • 在程序集的使用者和程序集的实现详细信息的使用者之间提供一定程度的间接性
  • 呈现程序集自述
元数据

元数据是一种二进制信息,它以非特定语言的方式描述在代码中定义的每一个类型和成员,程序集清单也是元数据的一部分

  • 类型的说明
  • 名称、可见性、基类和实现的接口。
  • 成员(方法、字段、属性、事件、嵌套的类型)
  • 属性
  • 修饰类型和成员的其他说明性元素
MSIL是微软的中间代码,它是实现类型元数据的中间代码,而资源就是我们程序中的图片、音乐文件等。

查看程序集

.NET提供了一个反编译工具ILDasm,在命令行窗口,输入ILDasm.exe,就可以打开这个反编译器

所有的 C#项目类型都会创建一个程序集,无论是类库还是可执行的EXE应用程序。在我们创建项目时,会自动生成源文件AssemblyInfo.cs,在这个文件中,可以使用一般的源代码编辑器编辑程序集的特性。

Assembiy类可以用于访问给定程序集的信息,它允许访问给定程序集的元数据。

[assembly: AssemblyTitle("ConsoleApp1")] 	//程序集的描述性名称
[assembly: AssemblyDescription("")]			//描述程序集或产品
[assembly: AssemblyConfiguration("")]		//指定建立信息,例如零售或者调试信息
[assembly: AssemblyCompany("")]				//指定公司名
[assembly: AssemblyProduct("ConsoleApp1")]	 //指定程序集所属产品的名称
[assembly: AssemblyCopyright("Copyright ©  2020")]	//包含版权和商标信息
[assembly: AssemblyTrademark("")]			
[assembly: AssemblyCulture("")]


[assembly: AssemblyVersion("1.0.0.0")]		//程序集的版本号
访问修饰符的作用域

访问修饰符

类内部

同一程序集的派生类

同一程序集的其他类

不同程序集的派生类

不同程序集的其他类

private

可以

不可以

不可以

不可以

不可以

protected

可以

可以

不可以

可以

不可以

internal

可以

可以

可以

不可以

不可以

public

可以

可以

可以

可以

可以

反射

用ILDasm工具浏览一个dll和exe的构成,这种机制称为反射。它用于在运行时通过编程方式获得类型信息。

在编程中经常用到,输入一个类型,然后输入"."时,就会出现一个列表,显示这个类型的属性、方法、事件等。这都是利用反射机制

反射可以获取已加载的程序集和在其中定义的类型(如类、接口和值类型)信息。也可以使用反射在运行时创建类型实例,以及调用和访问这些实例。

反射的一个主要功能就是查找程序集的信息。引用using System.Reflection;

string version = Assembly.LoadFile(@"F:\S2\预习\10章\WindowsFormsApp1\WindowsFormsApp1\bin\Debug\WindowsFormsApp1.exe").GetName().Version.ToString();
            Console.WriteLine(version);

Assembly.LoadFile(string path)方法用于通过文件路径加载程序集,其中path必须为完整物理路径。

程序集对象的GetType()方法可以得到程序集全部类型信息的数组。另外还有GetType(string name)可以得到指定的类型信息

Type是.Net定义的表示类型的类,位于System命名空间

属性

说明

Namespace

获取Type的命名空间

Name

数据类型名

FullName

获取该类型的完全限定名称,包括其命名空间,但不包括程序集

BaseType

获取当前Type直接从中继承的类型

返回值

方法

说明

PropertyInfo

GetProperty(string name)

搜索具有指定名称的公共属性

PropertyInfo[]

GetProperties()

返回为当前Type的所有公共属性

MethodInfo

GetMethod(string name)

搜索具有指定名称的公共方法

MethodInfo[]

GetMethods()

返回为当前Type的所有公共方法

通过实例对象的GetType()方法获取类型信息

//创建一个对象
            Stu obj = new Stu();
            //获取类型信息
            Type t = obj.GetType();

通过类获取类型信息

//通过类获取类型信息
            Type tp = typeof(Stu);

Type的常用方法和属性

  • Namespace 命名空间
  • FullName 完全限定名(命名空间加类名)
  • Name 类名
  • GetFields()方法获取公开的字段
  • GetField(string fielsName) 获取特定名称字段
  • GetProperties() 获取所有公开的属性
  • GetProperty(string propertyName) 获取特定名称的属性
  • GetMethods() 获取方法(包括继承和属性访问器)
  • 属性的本质就是方法 set,get是方法
  • GetMethod("Show") 获取特定名称的方法

MethodInfo类型常用方法

  • Invoke(对象,object[] 参数列表) 调用
动态引用一个dll文件 类库
Assembly ass = Assembly.LoadFile(Environment.CurrentDirectory + @"\libs\StudentManager.UserManager.dll");

Environment.CurrentDirectory 获取程序集的完全限定路径,exe文件所在的文件夹。会随着exe文件的移动而变化

foreach (var type in ass.GetTypes())
            {
                Console.WriteLine(type.Name);
                Console.WriteLine(type.FullName);
                Console.WriteLine(type.IsClass);  //是否是类
                Console.WriteLine(type.IsEnum);  //是否是枚举 
                Console.WriteLine(type.IsAbstract);  //是否是抽象类
                Console.WriteLine(type.IsInterface);  //是否是接口
            }

Assembly类中的GetTypes()方法可以获取(反射)类库中定义的全部声明类型

通过一系列的is属性进行过滤,取出想要的目标类型

var user = ass.GetTypes().First(m => !m.IsAbstract && m.IsClass);
            Console.WriteLine(user.FullName);

获得类型,没有此对象

如何创建对象
//没有对象
            var user = ass.GetTypes().First(m => !m.IsAbstract && m.IsClass);
//通过绝对路径在程序集中找到指定类型,创建它的实例
            object obj = ass.CreateInstance(user.FullName);

            //给对象赋值
            user.GetProperty("Name").SetValue(obj, "张三");
            user.GetProperty("Age").SetValue(obj, 22);
            user.GetMethod("SayHi").Invoke(obj, new object[0]);

Assembly类的CreateInstance()通过给绝对路径,在程序集中找到指定类型,创建实例

使用窗体进行文件更新。小例子
private void button1_Click(object sender, EventArgs e)
        {
            openFileDialog1.ShowDialog();
            //新的类库地址
            label1.Text = openFileDialog1.FileName;
        }
       //替换
        private void button2_Click(object sender, EventArgs e)
        {
            openFileDialog1.ShowDialog();
            //要替换的文件地址
            label2.Text = openFileDialog1.FileName;
            //提示是否替换
            var result =  MessageBox.Show("是否升级替换","升级提示",MessageBoxButtons.YesNo);
            if (result == DialogResult.Yes)
            {
                //删除旧的文件
                File.Delete(label2.Text);
                //复制新的文件
                File.Copy(label1.Text, label2.Text);
                MessageBox.Show("更新成功");
            }
        }