Administrator
发布于 2024-07-19 / 15 阅读
0
0

C# Attributes,Serializable(序列化)

特性提供了一种将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联的强大方法。将属性与程序实体关联后,可以使用反射技术在运行时查询该属性。

反射可以创建类型对象,比如程序集、模块。可以使用反射来动态创建类型的实例,将类型绑定到现有对象,或者从现有对象获取类型并调用其方法或访问其字段和属性。如果您在代码中使用特性,则反射可以访问它们并做相应的逻辑。

这些可以实现一些灵活多变的应用程序,比如图形编程,无代码编程,复杂动态变化实现简化等。

创建特性

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
      public class AttTestAttribute : Attribute
        {
            public string Desc { get; set; }
            public AttTestAttribute()
            {
                Console.WriteLine("AttTestAttribute 无参构造函数");
            }
            public AttTestAttribute(string desc)
            {
                this.Desc = desc;
                Console.WriteLine("有参构造函数");

            }
        }
        public static void Main()
        {
            string s = "asdasd";
            Console.WriteLine(s);
            AttTestAttribute a = new AttTestAttribute(s);
            s = Console.ReadLine();
        }
    }
}

使用范围

assembly Entire assembly// 程序集
module Current assembly module // 组件
field Field in a class or a struct // 字段
event Event // 事件
method Method or get and set property accessors // 方法
param Method parameters or set property accessor parameters // 方法的参数
property Property // 属性
return Return value of a method, property indexer, or get property accessor // 方法的返回值
type Struct, class, interface, enum, or delegate // 结构体 接口 枚举 委托 等

C#中序列化

序列化(Serialization),也叫串行化。通过将对象转换为字节流,从而存储对象到内存,数据库或文件的过程。主要用途是保存对象的状态数据,以便进行传输和需要时重建对象。对象的状态主要包括字段和属性(不包含方法和事件)传递数据,保存数据(持久化数据)

我们都知道对象是暂时保存在内存中的,不能用U盘考走了,有时为了使用介质转移对象,并且把对象的状态保持下来,就需要把对象保存下来,这个过程就叫做序列化,通俗点,就是把人的魂(对象)收伏成一个石子(可传输的介质)

反序列化(Deserialization):将序列化后的数据重新解析成类型的实例

就是再把介质中的东西还原成对象,把石子还原成人的过程。
在进行这些操作的时候都需要这个可以被序列化,要能被序列化,就得给类头加[Serializable]特性。
通常网络程序为了传输安全才这么做

关于序列化和反序列化的执行过程和原理

持久存储

我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据。尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂。可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码,以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。

公共语言运行时 (CLR) 管理对象在内存中的分布,.NET 框架则通过使用反射提供自动的序列化机制。对象序列化后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其他实例的引用。类序列化后,序列化引擎将跟踪所有已序列化的引用对象,以确保同一对象不被序列化多次。.NET 框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。对对象图表的唯一要求是,由正在进行序列化的对象所引用的所有对象都必须标记为 Serializable(请参阅基本序列化)。否则,当序列化程序试图序列化未标记的对象时将会出现异常。

当反序列化已序列化的类时,将重新创建该类,并自动还原所有数据成员的值。

按值封送 对象仅在创建对象的应用程序域中有效。除非对象是从 MarshalByRefObject 派生得到或标记为 Serializable,否则,任何将对象作为参数传递或将其作为结果返回的尝试都将失败。如果对象标记为 Serializable,则该对象将被自动序列化,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对象的一个精确副本。此过程通常称为按值封送。

如果对象是从 MarshalByRefObject 派生得到,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身。也可以将从 MarshalByRefObject 派生得到的对象标记为 Serializable。远程使用此对象时,负责进行序列化并已预先配置为 SurrogateSelector 的格式化程序将控制序列化过程,并用一个代理替换所有从 MarshalByRefObject 派生得到的对象。如果没有预先配置为 SurrogateSelector,序列化体系结构将遵从下面的标准序列化规则(请参阅序列化过程的步骤)

.Net序列化流程:

  1. 将支持序列化的类型进行实例化成对象。

  2. 通过使用Formatter/Serializer将被序列化对象进行序列化编码。

  3. 然后存入指定的流中。

参与序列化过程的对象详细说明:

  1. 被序列化的对象
    可以是一个对象,也可是一个集合
    需要被序列化的类型需要使用SerializableAttribute 特性修饰
    类型中不可序列化的成员需要使用NonSerializedAttribute 特性修饰

  2. 包含已序列化对象的流对象
    比如:文件流、内存流

  3. Fromatter/Serializer类型
    用于给被序列化的对象进行序列化
    序列化和反序列化对象所使用的Fromatter/Serializer主要是以下类型

System.Text.Json.JsonSerializer System.Xml.Serialization.XmlSerializer System.Runtime.Serialization.Formatters.Binary.BinaryFormatter System.Runtime.Serialization.DataContractSerializer System.Runtime.Serialization.Json.DataContractJsonSerializer

常用格式;

JSON(JavaScript Object Notation)
比较紧凑,适合用于传输
现在常用于Web传输数据

XML(eXtensible Markup Language)
表示数据更加直观,可读性更好
但也导致容量更大

Binary
在二进制序列化中,所有内容都会被序列化
使用二进制编码来生成精简的序列化
可以用于基于存储或socket的网络流

详细序列化过程参考:

.NET中的序列化和反序列化详解 - 重庆熊猫 - 博客园 (cnblogs.com)

C# 特性(Attribute)之Serializable特性 - 郑小超 - 博客园 (cnblogs.com)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization; //IFormatter(接口)的命名空间
using System.Runtime.Serialization.Formatters.Binary;
//BInaryFormatter的命名空间 BinaryFormatter()以二进制格式序列化和反序列化对象或连接对象的整个图形
using System.IO;
//stream类的命名空间提供字节序列的一般视图。 这是一个抽象类
//FileStream类 为文件提供 Stream,既支持同步读写操作,也支持异步读写操作。
//继承Object MarshalByRefObject Stream FileStream
namespace ConsoleApp1
{
    class Program
    {
        [Serializable] //通过添加[Serializable]特性确保当前类可以被实例化
        public class MyObject
        {
            public int n1 { get; set; }
            public int n2 { get; set; }
            public string str { get; set; }
        }
        //将类实例序列化进流,代码如下:
        public static void SerializableObj()
        {
            MyObject obj = new MyObject();
            obj.n1 = 1;
            obj.n2 = 24;
            obj.str = "一些字符串";
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None);
            formatter.Serialize(stream, obj);
            stream.Close();
        }
        /*
         本例使用二进制格式化程序进行序列化
        创建一个要使用的流和格式化程序的实例,然后调用格式化程序的 Serialize 方法。流和要序列化的对象实例作为参数提供给此调用。
        类中的所有成员变量(甚至标记为 private 的变量)都将被序列化,但这一点在本例中未明确体现出来。
        在这一点上,二进制序列化不同于只序列化公共字段的 XML 序列化程序
         */
        //将对应的类实例进行反序列化
        public static MyObject DeSerializableObj()
        {
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream("MyFile.bin", FileMode.Open, FileAccess.Read, FileShare.Read);
            MyObject Obj = (MyObject)formatter.Deserialize(stream);
            stream.Close();
            return Obj;
        }
        public static void Main(string[] arg) 
        {
            SerializableObj();
            MyObject obj = DeSerializableObj();
            Console.WriteLine("n1: {0}", obj.n1);
            Console.WriteLine("n2: {0}", obj.n2);
            Console.WriteLine("str: {0}", obj.str);
            string s = Console.ReadLine();
        }
    }
}

说明整个序列化和反序列化的过程成功!!!注意:需要序列化的类必须将[Serializable]特性,否则会报错!!!

上面所使用的 BinaryFormatter 效率很高,能生成非常紧凑的字节流。所有使用此格式化程序序列化的对象也可使用它进行反序列化,对于序列化将在 .NET 平台上进行反序列化的对象,此格式化程序无疑是一个理想工具。需要注意的是,对对象进行反序列化时并不调用构造函数。对反序列化添加这项约束,是出于性能方面的考虑。但是,这违反了对象编写者通常采用的一些运行时约定,因此,开发人员在将对象标记为可序列化时,应确保考虑了这一特殊约定。

补充C#:+(特性 )+AttitudeC#(类)前面或者(方法)前面 (中括号)定义

属性(Property)跟特性(Attribute)弄混淆,其实这是两种不同的东西。属性就是面向对象思想里所说的封装在类里面的数据字段,其形式为:

public class HumanBase
{
	public string Name { get; set; }
	public int Age { get; set; }
	public int Gender { get; set; }
 }

在HumanBase这个类里出现的字段都叫属性(Property),而特性(Attribute)又是怎样的呢?

[Serializable]
public class HumanBase
{
	public string Name { get; set; }
	public int Age { get; set; }
	public int Gender { get; set; }
 }

我们在HumanBase类声明的上一行加了一个[Serializable],这就是特性(Attribute),它表示HumanBase是可以被序列化的,这对于网络传输是很重要的

C#的特性可以应用于各种类型和成员。前面的例子将特性用在类上就可以被称之为“类特性”,同理,如果是加在方法声明前面的就叫方法特性。无论它们被用在哪里,无论它们之间有什么区别,特性的最主要目的就是自描述。并且因为特性是可以由自己定制的,而不仅仅局限于.NET提供的那几个现成的,因此给C#程序开发带来了相当大的灵活性和便利。

假设有一天你去坐飞机,你就必须提前去机场登机处换登机牌。登机牌就是一张纸,上面写着哪趟航班、由哪里飞往哪里以及你的名字、座位号等等信息,其实,这就是特性。它不需要你生理上包含这些属性(人类出现那会儿还没飞机呢),就像上面的HumanBase类没有IsSerializable这样的属性,特性只需要在类或方法需要的时候加上去就行了,就像你不总是在天上飞一样。(不需要类上有这个特性,只需要有个指引就行)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        [Serializable]
         public class HumanBase
        {
            public string Name { get; set;}
            public int Age { get; set; }
            public int Cender { get; set; }
			public void Run(int speed)
			{
  				//Running is good
			}
        }
        public static void Main(string[] arg) 
        {
            Console.WriteLine(typeof(HumanBase).IsSerializable); //检验是否可序列化的
            string s = Console.ReadLine();
        }
    }
}

只要是个四肢健全、身体健康的人就可以跑步,那这么说,跑步就是有前提条件的,至少是四肢健全,身体健康。由此可见,残疾人和老年人如果跑步就会出问题。假设一个HumanBase的对象代表的是一位耄耋老人,如果让他当刘翔的陪练,那就直接光荣了。如何避免这样的情况呢,我们可以在Run方法中加一段逻辑代码,先判断Age大小,如果小于2或大于60直接抛异常,但是2-60岁之间也得用Switch来分年龄阶段地判断speed参数是否合适,那么逻辑就相当臃肿。简而言之,如何用特性表示一个方法不能被使用呢

        [Obsolete("I am ikun,cant run",true)]
        public virtual void Run(int speed)
        {
            // Running is good
        }

首先,特性也是类。不同于其它类的是,特性都必须继承自System.Attribute类,否则编译器如何知道谁是特性谁是普通类呢。当编译器检测到一个类是特性的时候,它会识别出其中的信息并存放在元数据当中,仅此而已,编译器并不关心特性说了些什么,特性也不会对编译器起到任何作用,正如航空公司并不关心每个箱子要去哪里,只有箱子的主人和搬运工才会去关心这些细节。假设我们现在就是航空公司的管理人员,需要设计出前面提到的登机牌,那么很简单,我们先看看最主要的信息有哪些:

  public class BoardingCheckAttribute : Attribute
        {
            public string ID { get; private set; }
            public string Name { get; private set; }
            public int FlightNumber { get; private set; }
            public int PostionNumber { get; private set; }
            public string Departure { get; private set; }
            public string Destination { get; private set; }
        }

我们简单列举这些属性作为航空公司登机牌上的信息,用法和前面的一样,贴到HumanBase上就行了,说明此人具备登机资格。这里要简单提一下,你可能已经注意到了,在使用BoardingCheckAttribute的时候已经把Attribute省略掉了,不用担心,这样做是对的,因为编译器默认会自己加上然后查找这个属性类的。哦,等一下,我突然想起来他该登哪架飞机呢?显然,在这种需求下,我们的特性还没有起到应有的作用,我们还的做点儿工作,否则乘客面对一张空白的机票一定会很迷茫。

于是,我们必须给这个特性加上构造函数,因为它不仅仅表示登机的资格,还必须包含一些必要的信息才行:


评论