Оказывается, это не так сложно, как может показаться на первый взгляд. Ключевая информация здесь заключается в том, что упомянутое исключение выдается не XamlReader
, а скорее XamlObjectWriter
, который отвечает за потребление XamlReader
и создание и заполнение результирующего объекта. Так что все, что нам нужно сделать, это предоставить настроенный XamlReader
, который бы просто пропускал неизвестные свойства. На мой взгляд, самый универсальный подход заключается в создании читателя, который бы обернулся вокруг другого (произвольного) читателя. Идея может быть обобщена следующим образом:
- В методе
Read
мы читаем один раз из основного читателя
- Если мы сталкиваемся с неизвестным свойством, которое можно легко определить, исследуя
XamlReader.Member.IsUnknown
всякий раз, когда XamlReader.NodeType
равен StartMember
, мы просто продолжаем чтение, пока не достигнем конца определения члена (a соответствует 1 EndMember
узел) и перейти к следующему узлу, прочитав еще раз; если следующий узел также является неизвестным свойством, мы повторяем процедуру
Таким образом, один вызов Read
пропустит неизвестные свойства, что может привести к многократному чтению из основного читателя, но это поведение будет прозрачным для потребителя.
Вот пример кода:
public class LaxXamlReader : XamlReader
{
public override bool Read()
{
//Read once from the underlying reader
_Reader.Read();
//Check if current node is an unknown property
while (NodeType == XamlNodeType.StartMember && Member.IsUnknown)
{
//We need to track member nesting level so that we can correctly
//identify the corresponding EndMember node
var level = 1;
while (level > 0)
{
_Reader.Read();
if (NodeType == XamlNodeType.StartMember)
level++;
else if (NodeType == XamlNodeType.EndMember)
level--;
}
//At this point we're at the corresponsing EndMember node, so we
//advance to the next node; if it's also an unknown property, it
//will be caught by the while loop
_Reader.Read();
}
//If we've reached the end of input return false
return !IsEof;
}
public override XamlReader ReadSubtree()
=> new LaxXamlReader(_Reader.ReadSubtree());
protected override void Dispose(bool disposing)
{
//Only dispose the underlying reader if Dispose() was called;
//otherwise let GC do the job
if (disposing)
((IDisposable)_Reader).Dispose();
base.Dispose(disposing);
}
//The code below simply forwards the functionality from the underlying reader
public LaxXamlReader(XamlReader reader)
{
_Reader = reader;
}
private readonly XamlReader _Reader;
public override bool IsEof => _Reader.IsEof;
public override XamlMember Member => _Reader.Member;
public override NamespaceDeclaration Namespace => _Reader.Namespace;
public override XamlNodeType NodeType => _Reader.NodeType;
public override XamlSchemaContext SchemaContext => _Reader.SchemaContext;
public override XamlType Type => _Reader.Type;
public override object Value => _Reader.Value;
public override void Skip() => _Reader.Skip();
}
Пример использования:
var xaml = "<Object Foo=\"Bar\" xmlns=\"clr-namespace:System;assembly=mscorlib\" />";
var obj = XamlServices.Load(new LaxXamlReader(new XamlXmlReader(new StringReader(xaml))));
Обратите внимание, что XamlReader
(наряду с другими упомянутыми XAML связанными типами) - это тип, определенный в пространстве имен System.Xaml
.
1 Поскольку свойства в XAML могут быть записаны как элементы и содержать объекты со своими собственными свойствами, нам нужно игнорировать EndMember
узлы, соответствующие этим вложенным свойствам