C# 与IDeserializationCallback.OnDeserialization等效的XAML读取器

C# 与IDeserializationCallback.OnDeserialization等效的XAML读取器,c#,.net,visual-studio,xaml,deserialization,C#,.net,Visual Studio,Xaml,Deserialization,我已经向一个持久化类添加了新字段,并且需要确保在从磁盘加载旧版本的XAML序列化文件时将这些字段设置为合理的默认值。以前,对于BinaryFormatter,我将使用OnDeserialization方法来确定在将新字段添加到持久化类(使用OptionalField AttAttribute)时应设置的默认值。例如: 我希望确保协议类型(在上面的示例代码中)中不包含新可选字段的旧文件在中具有合理的默认值。我四处搜寻,但似乎找不到任何明显的东西(例如)是否有任何等价物?XamlServices在内

我已经向一个持久化类添加了新字段,并且需要确保在从磁盘加载旧版本的XAML序列化文件时将这些字段设置为合理的默认值。以前,对于BinaryFormatter,我将使用OnDeserialization方法来确定在将新字段添加到持久化类(使用OptionalField AttAttribute)时应设置的默认值。例如:


我希望确保协议类型(在上面的示例代码中)中不包含新可选字段的旧文件在中具有合理的默认值。我四处搜寻,但似乎找不到任何明显的东西(例如)是否有任何等价物?

XamlServices
在内部使用。此类型有一个包含各种回调的参数。这些内容不会被
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
包含正在反序列化的对象。不确定哪个回调最适合您的目的。它们更等同于
[OnDeserialization]
/
[OnDeserialization]
属性,因为它们是在反序列化单个对象时调用的,而不是在整个图形完成后调用的,如
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;
        }
    }
}
public类CallbackXamlService
{
//XamlService使用的默认设置
公共XmlWriterSettings XmlWriterSettings{get;set;}
=新的XmlWriterSettings{Indent=true,ommitXmlDeclaration=true};
反序列化之前的公共事件事件处理程序;
反序列化后的公共事件事件处理程序;
序列化之前的公共事件事件处理程序;
//后序列化事件似乎不太可能,见下文
公共对象装入XAML(文本阅读器文本阅读器)
{
var设置=新的XamlObjectWriterSettings
{
BeforePropertiesHandler=(s,e)=>BeforeDeserialize?调用(this,e),
AfterPropertiesHandler=(s,e)=>反序列化后?.Invoke(this,e)
};
使用(var xmlReader=xmlReader.Create(textReader))
使用(var-xamlReader=new-XamlXmlReader(xmlReader))
使用(var xamlWriter=new XamlObjectWriter(xamlReader.SchemaContext,设置))
{
Transform(xamlReader、xamlWriter);
返回xamlWriter.Result;
}
}
公共字符串SaveXaml(对象实例)
{
var stringBuilder=新的stringBuilder();
使用(var textWriter=新的StringWriter(stringBuilder))
SaveXaml(textWriter,实例);
返回stringBuilder.ToString();
}
public void SaveXaml(TextWriter TextWriter,对象实例)
{
Action BeforeSerialization=(obj)=>BeforeSerialization?.Invoke(这是新的XamlObjectEventArgs(obj));
//XamlObjectReaderSettings上没有等效的回调
//使用派生的XamlObjectReader来跟踪处理过的对象
使用(var xmlWriter=xmlWriter.Create(textWriter,XmlWriterSettings))
使用(var xamlXmlWriter=new xamlXmlWriter(xmlWriter,new XamlSchemaContext())
使用(var xamlObjectReader=newcallbackxamlobjectreader(实例,xamlXmlWriter.SchemaContext,null,在序列化之前))
{
Transform(xamlObjectReader,xamlXmlWriter);
xmlWriter.Flush();
}
}
私有类回调XamlObjectReader:XamlObjectReader
{
序列化{get;}之前的公共操作
//私有堆栈实例堆栈=新堆栈();
公共回调XamlObjectReader(对象实例、XamlSchemaContext、XamlObjectReaderSettings设置、序列化前的操作)
:base(实例、schemaContext、设置)
{
BeforeSerialization=BeforeSerialization;
}
公共覆盖bool Read()
{
if(base.Read())
{
if(NodeType==XamlNodeType.StartObject)
{
//instanceStack.Push(实例);
序列化之前?.Invoke(实例);
}
//未在EndObject节点上设置XamlObjectReader.Instance
//当使用数组和字典等类型时,EndObject节点不会与StartObject节点对齐
//都涉及,因此使用堆栈跟踪当前实例并不总是有效。
//不知道是否有一种可靠的方法可以在没有可能易碎的特殊外壳的情况下修复,
//XamlObjectReader的内部结构极其复杂。
//else if(NodeType==XamlNodeType.EndObject)
//{
//对象实例=instanceStack.Pop();
//后序列化(实例);
//}
返回true;
}
返回false;
}
}
}

感谢您解释此方法,我已经实现了它,它似乎工作得很好。我很好奇,您知道在将对象保存到XAML之前是否也有类似的操作?我已尝试反转方法调用以使用XmlWriter等,但仍停留在应获取SchemaContext的位置…@Jeb
XamlService
只需将新的XamlSchemaContext传递给
XamlXmlWriter
。不幸的是,
XamlObjectReader
没有相同类型的回调,但是我用一种变通方法编辑了答案。
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;
    }
}
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;
        }
    }
}