我已经向持久化类添加了新字段,并且需要确保在从磁盘加载旧版本的XAML序列化文件时将它们设置为合理的默认值。以前,在使用BinaryFormatter时,我会使用OnDeserialization方法来确定在持久化类中添加了新字段时应该设置哪些默认值(使用OptionalField属性)。例如:
/// <summary>
/// Runs when the entire object graph has been deserialized.
/// </summary>
/// <param name="sender">The object that initiated the callback. The functionality for this parameter is not currently implemented.</param>
public override void
OnDeserialization
(object sender)
{到目前为止,在写入XAML文件时,我似乎找不到任何等效的东西,例如:
using (TextReader reader = File.OpenText(filePath))
{
protocol = (Protocol)XamlServices.Load(reader);
}我希望确保在协议类型中不包含新的可选字段的旧文件(在上面的示例代码中)具有合理的默认值。我到处寻找,但似乎找不到任何明显的东西(例如https://ludovic.chabant.com/devblog/2008/06/25/almost-everything-you-need-to-know-about-xaml-serialization-part-2/),有没有类似的东西?
发布于 2019-03-11 23:32:55
XamlServices在内部使用XamlObjectWriter。此类型有一个包含各种回调的XamlObjectWriterSettings参数。这些都不是XamlServices公开的,但是它的功能很容易被复制。
我没有对此进行过广泛的测试,但这似乎是有效的:
public static object LoadXaml(TextReader textReader)
{
var settings = new XamlObjectWriterSettings
{
AfterBeginInitHandler = (s, e) => Debug.Print($"Before deserializing {e.Instance}"),
AfterEndInitHandler = (s, e) => Debug.Print($"After deserializing {e.Instance}")
};
using (var xmlReader = XmlReader.Create(textReader))
using (var xamlReader = new XamlXmlReader(xmlReader))
using (var xamlWriter = new XamlObjectWriter(xamlReader.SchemaContext, settings))
{
XamlServices.Transform(xamlReader, xamlWriter);
return xamlWriter.Result;
}
}e.Instance包含被反序列化的对象。不确定哪个回调最适合您的目的。它们更等同于[OnDeserializing]/[OnDeserialized]属性,因为它们是在单个对象反序列化时调用的,而不是像IDeserializationCallback.OnDeserialization那样在整个图完成之后调用。
下面是一个在序列化期间提供事件的类的更完整的实现。XamlObjectReader不像XamlObjectWriter那样支持回调,所以这使用了一种变通方法。由于注释中解释的原因,它只在序列化对象之前而不是在序列化对象之后引发事件。
public class CallbackXamlService
{
// Default settings that XamlService uses
public XmlWriterSettings XmlWriterSettings { get; set; }
= new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };
public event EventHandler<XamlObjectEventArgs> BeforeDeserializing;
public event EventHandler<XamlObjectEventArgs> AfterDeserializing;
public event EventHandler<XamlObjectEventArgs> BeforeSerializing;
// AfterSerializing event doesn't seem to be easily possible, see below
public object LoadXaml(TextReader textReader)
{
var settings = new XamlObjectWriterSettings
{
BeforePropertiesHandler = (s, e) => BeforeDeserializing?.Invoke(this, e),
AfterPropertiesHandler = (s, e) => AfterDeserializing?.Invoke(this, e)
};
using (var xmlReader = XmlReader.Create(textReader))
using (var xamlReader = new XamlXmlReader(xmlReader))
using (var xamlWriter = new XamlObjectWriter(xamlReader.SchemaContext, settings))
{
XamlServices.Transform(xamlReader, xamlWriter);
return xamlWriter.Result;
}
}
public string SaveXaml(object instance)
{
var stringBuilder = new StringBuilder();
using (var textWriter = new StringWriter(stringBuilder))
SaveXaml(textWriter, instance);
return stringBuilder.ToString();
}
public void SaveXaml(TextWriter textWriter, object instance)
{
Action<object> beforeSerializing = (obj) => BeforeSerializing?.Invoke(this, new XamlObjectEventArgs(obj));
// There are no equivalent callbacks on XamlObjectReaderSettings
// Using a derived XamlObjectReader to track processed objects instead
using (var xmlWriter = XmlWriter.Create(textWriter, XmlWriterSettings))
using (var xamlXmlWriter = new XamlXmlWriter(xmlWriter, new XamlSchemaContext()))
using (var xamlObjectReader = new CallbackXamlObjectReader(instance, xamlXmlWriter.SchemaContext, null, beforeSerializing))
{
XamlServices.Transform(xamlObjectReader, xamlXmlWriter);
xmlWriter.Flush();
}
}
private class CallbackXamlObjectReader : XamlObjectReader
{
public Action<object> BeforeSerializing { get; }
//private Stack<object> instanceStack = new Stack<object>();
public CallbackXamlObjectReader(object instance, XamlSchemaContext schemaContext, XamlObjectReaderSettings settings, Action<object> beforeSerializing)
: base(instance, schemaContext, settings)
{
BeforeSerializing = beforeSerializing;
}
public override bool Read()
{
if (base.Read())
{
if (NodeType == XamlNodeType.StartObject)
{
//instanceStack.Push(Instance);
BeforeSerializing?.Invoke(Instance);
}
// XamlObjectReader.Instance is not set on EndObject nodes
// EndObject nodes do not line up with StartObject nodes when types like arrays and dictionaries
// are involved, so using a stack to track the current instance doesn't always work.
// Don't know if there is a reliable way to fix this without possibly fragile special-casing,
// the XamlObjectReader internals are horrendously complex.
//else if (NodeType == XamlNodeType.EndObject)
//{
// object instance = instanceStack.Pop();
// AfterSerializing(instance);
//}
return true;
}
return false;
}
}
}https://stackoverflow.com/questions/55104374
复制相似问题