通用 C# .NET 对象保存/加载






4.36/5 (18投票s)
你的工具箱中有用的代码 - 通用地在 XML 中保存/加载对象
引言
我参与的大部分项目都需要保存/加载某种对象。 无论是存储应用程序特定设置的自定义对象(不,我不想把它们塞进 app.config,非常感谢 - 这样会很快变得混乱),还是我在应用程序中使用的一些轻量级列表。 不可避免地,最终都需要将这个东西保存到某个地方,然后再加载回来 - 这应该很简单,所以我不想每次都重写代码,或者调用一些重量级的库来管理这些事情。 这篇简短的文章描述了一些非常简单的代码,这些代码已经成为我“代码工具箱”的常规组成部分 - 我几乎每隔一天都会使用它来处理涉及序列化的事情。
分享是美德,请尽情享用。 :)
注意:虽然这里描述的代码是用 C# 编写的,但我还在下载链接中提供了一个 VB 版本。
背景 - 过去的方式
首先,让我们从一个我们想要序列化的非常基本的示例类开始。 你知道的 - 基本的东西,它有一个构造函数和一个指示我希望保存位置的 const
。
class SimpleClass
{
const string FileSavePath = @"c:\data\SimpleClass.xml";
public Guid ID { get; set; }
public string CityName { get; set; }
public int Rank { get; set; }
public bool Active { get; set; }
public List<string> RandomList { get; set; }
public SimpleClass()
{
ID = Guid.NewGuid();
RandomList = new List<string>();
}
}
过去,当我想保存和加载这样的对象时,我会构建自定义的保存和加载方法,调用 XMLSerializer
类将我的对象转换为 XML 表示形式,以及流读取器/写入器来从/向磁盘获取 XML。 我相信你自己也偶尔会做同样的事情。
(1) 基本对象保存方法
public void BasicSave() {
var xs = new XmlSerializer(typeof(SimpleClass));
using (TextWriter sw = new StreamWriter(FileSavePath))
{
xs.Serialize(sw, this);
}
}
(2) 基本对象加载方法
public void BasicLoad()
{
var xs = new XmlSerializer(typeof(SimpleClass));
using (var sr = new StreamReader(FileSavePath))
{
var tempObject = (SimpleClass)xs.Deserialize(sr);
ID = tempObject.ID;
CityName = tempObject.CityName;
Rank = tempObject.Rank;
Active = tempObject.Active;
RandomList = tempObject.RandomList;
}
}
这些方法很好,它们可以工作,但我每次重用时都必须重新编写和自定义它们……不太好 - 我们可以做得更好!
稍微抽象一下
下一步是创建一个单独的类来管理保存/加载。 这样,从重用的角度来看,事情已经变得更好了。 在这里,我们有一个类 GenericUtils
,以及保存方法。
该类是 static
的,所以我不需要隐式地创建它。 Save
方法接受一个 Object 类型 "T
",和两个方法参数:'FileName
' - 我们希望将序列化对象保存到的位置,和 'Object
',我们将要序列化的对象本身。 该方法是原始 save
方法的简单扩展。 主要区别在于,我们不再使用类 'SImpleObject
' 显式转换,而是提取变量 T
的 typeof
并将其用作 XmLSerializer
的输入参数。
public static class GenericUtils
{
public static bool Save<T>(string FileName, Object Obj)
{
var xs = new XmlSerializer(typeof(T));
using (TextWriter sw = new StreamWriter(FileName))
{
xs.Serialize(sw, Obj);
}
if (File.Exists(FileName))
return true;
else return false;
}
}
现在,当我们想要保存时,我们调用通用方法,传入对象类型 <SimpleClass>
,我们想要保存到的 fileName
,以及对正在保存的对象的引用 this
。
public bool SaveGeneric1(string fileName)
{
return GenericUtils.Save<SimpleClass>(fileName, this);
}
到目前为止一切顺利,让我们看看 Load
方法。
Load 返回一个通用 Object,并通过传入一个对象类型和我们想要访问的对象之前保存的文件的路径/名称来调用。 它与之前的代码相同,只是被通用化和抽象化了。
Load
方法检查请求的文件是否存在,如果存在,则尝试反序列化它。 这里的关键技巧是,我们使用在调用该方法时传入的通用 T
值来对反序列化器的对象类型进行类型转换。
public static class GenericUtils
{
public static T Load<T>(string FileName)
{
Object rslt;
if (File.Exists(FileName))
{
var xs = new XmlSerializer(typeof(T));
using (var sr = new StreamReader(FileName))
{
rslt = (T)xs.Deserialize(sr);
}
return (T)rslt;
}
else
{
return default(T);
}
}
}
与保存一样,现在当我们想要加载时,我们调用通用方法,传入对象类型 <SimpleClass>
,我们要加载的序列化对象所在的 fileName
,以及对正在保存的对象的引用 this
。
public void LoadGeneric1(string fileName)
{
var fileData = GenericUtils.Load<SimpleClass>(fileName);
ID = fileData.ID;
CityName = fileData.CityName;
Rank = fileData.Rank;
Active = fileData.Active;
}
好的,这稍微干净了一点,但我们可以做得更好!
当我们保存对象时,我们总是将类型发送到保存方法。 没有必要这样做。 我们可以在保存之前立即读取类型。 这可以减少更多代码
public static bool Save2(string FileName, Object Obj)
{
var xs = new XmlSerializer(Obj.GetType());
using (TextWriter sw = new StreamWriter(FileName))
{
xs.Serialize(sw, Obj);
}
if (File.Exists(FileName))
return true;
else return false;
}
这里的变化是,当我们声明 XmlSerializer
时,我们不是传入被调用的方法接收的类型,而是动态地获取类型 Obj.GetType()
。
这意味着该方法的调用代码更加简洁
public bool SaveGeneric2(string fileName)
{
return GenericUtils.Save2(fileName, this);
}
下一步...
好的 - 我知道在这里停止似乎很突然,但我现在必须发布这么多,因为它正在向一位正在转换为 C# 的同事描述一个概念,并同时分享学习经验! .... 一旦我有时间,我就会更新这篇文章,以展示如何使用 JSON 保存/加载,并且我们还将更深入地使用反射来实现对象的深层复制。
历史
- 第 1 版(大概!) - 2016 年 6 月 12 日