StringWriter mit anderem Encoding

Vor einer ganzen Weile habe ich mal die StringWriter-Klasse empfohlen.
Die StringWriter-Klasse nutzt intern UTF-16, wodurch zum Beispiel bei der Xml-Serialisierung von Klassen auch das Ergebnis in UTF-16 vorliegt. Dies ist nicht zwangsläufig gewünscht, bei mir hat dadurch ein Backend-Service gestreikt.
Um trotzdem den StringWriter weiter zu verwenden, muss man sich eine eigene Klasse schreiben, welche von der StringWriter ableitet und das Encoding-Property überschreibt:

using System;
using System.IO;
using System.Text;

namespace Utilities.IO
{

	/// <summary>
	/// 	A simple class derived from StringWriter, but which allows
	/// 	the user to select which Encoding is used. This is most
	/// 	likely to be used with XmlTextWriter, which uses the Encoding
	/// 	property to determine which encoding to specify in the XML.
	/// </summary>
	public class StringWriterWithEncoding : StringWriter
	{
		private Encoding _encoding;

		/// <summary>
		/// 	Initializes a new instance of the StringWriterWithEncoding class
		/// 	with the specified encoding.
		/// </summary>
		/// <param name = "encoding">The encoding to report.</param>
		public StringWriterWithEncoding(Encoding encoding)
			: base()
		{
			this._encoding = encoding;
		}

		/// <summary>
		/// 	Initializes a new instance of the StringWriter class with the 
		/// 	specified format control and encoding.
		/// </summary>
		/// <param name = "encoding">The encoding to report.</param>
		/// <param name = "formatProvider">An IFormatProvider object that controls formatting.</param>
		public StringWriterWithEncoding(Encoding encoding, IFormatProvider formatProvider)
			: base(formatProvider)
		{
			this._encoding = encoding;
		}

		/// <summary>
		/// 	Initializes a new instance of the StringWriter class that writes to the
		/// 	specified StringBuilder, and reports the specified encoding.
		/// </summary>
		/// <param name = "encoding">The encoding to report.</param>
		/// <param name = "sb">The StringBuilder to write to. </param>
		public StringWriterWithEncoding(Encoding encoding, StringBuilder sb)
			: base(sb)
		{
			this._encoding = encoding;
		}

		/// <summary>
		/// 	Initializes a new instance of the StringWriter class that writes to the specified 
		/// 	StringBuilder, has the specified format provider, and reports the specified encoding.
		/// </summary>
		/// <param name = "encoding">The encoding to report.</param>
		/// <param name = "sb">The StringBuilder to write to. </param>
		/// <param name = "formatProvider">An IFormatProvider object that controls formatting.</param>
		public StringWriterWithEncoding(Encoding encoding, StringBuilder sb, IFormatProvider formatProvider)
			: base(sb, formatProvider)
		{
			this._encoding = encoding;
		}

		/// <summary>
		/// 	Gets the Encoding in which the output is written.
		/// </summary>
		public override Encoding Encoding
		{
			get { return this._encoding; }
		}
	}
}

Update:
Habe die Klasse mal um Kommentare erweitert.

XML serialisierbares Dictionary

Ich verstehe zwar nicht, warum es standardmäßig nicht im .NET Framework implementiert ist, allerdings ist es ohne Hilfsmittel nicht möglich, eine Klasse mit einem Dictionary mittels XmlSerializer zu serialisieren! Mit folgender Hilfsklasse funktioniert es dahin anstandslos, vorausgesetzt sowohl TKey und TValue sind Xml-serialisierbar.

using System.Collections.Generic;
using System.Xml.Serialization;

namespace Utilities.Generic
{
	[XmlRoot("dictionary")]
	public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
	{
		#region Constructors

		public SerializableDictionary():base() {}

		public SerializableDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary) { }

		public SerializableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer) {}

		public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer) { }

		public SerializableDictionary(IEqualityComparer<TKey> comparer) : base(comparer) { }

		public SerializableDictionary(int capacity) : base(capacity) { }

		#endregion

		private const string ItemTagName = "item";
		private const string KeyTagName = "key";
		private const string ValueTagName = "value";

		/// <summary>
		/// Diese Methode ist reserviert und sollte nicht verwendet werden. Wenn Sie die IXmlSerializable-Schnittstelle implementieren, sollten Sie null (Nothing in Visual Basic) von der Methode zurückgeben und stattdessen das <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> auf die Klasse anwenden, wenn ein benutzerdefiniertes Schema erforderlich ist.
		/// </summary>
		/// <returns>
		/// Ein <see cref="T:System.Xml.Schema.XmlSchema"/> zur Beschreibung der XML-Darstellung des Objekts, das von der <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/>-Methode erstellt und von der <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/>-Methode verwendet wird.
		/// </returns>
		public System.Xml.Schema.XmlSchema GetSchema()
		{
			return null;
		}

		/// <summary>
		/// Generiert ein Objekt aus seiner XML-Darstellung.
		/// </summary>
		/// <param name="reader">Der <see cref="T:System.Xml.XmlReader"/>-Stream, aus dem das Objekt deserialisiert wird.</param>
		public void ReadXml(System.Xml.XmlReader reader)
		{
			XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
			XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

			bool wasEmpty = reader.IsEmptyElement;
			reader.Read();

			if (wasEmpty)
			{
				return;
			}

			while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
			{
				reader.ReadStartElement(ItemTagName);

				reader.ReadStartElement(KeyTagName);
				TKey key = (TKey)keySerializer.Deserialize(reader);
				reader.ReadEndElement();

				reader.ReadStartElement(ValueTagName);
				TValue value = (TValue)valueSerializer.Deserialize(reader);
				reader.ReadEndElement();

				this.Add(key, value);

				reader.ReadEndElement();
				reader.MoveToContent();
			}
			reader.ReadEndElement();
		}

		/// <summary>
		/// Konvertiert ein Objekt in seine XML-Darstellung.
		/// </summary>
		/// <param name="writer">Der <see cref="T:System.Xml.XmlWriter"/>-Stream, in den das Objekt serialisiert wird.</param>
		public void WriteXml(System.Xml.XmlWriter writer)
		{
			XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
			XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

			foreach (TKey key in this.Keys)
			{
				writer.WriteStartElement(ItemTagName);

				writer.WriteStartElement(KeyTagName);
				keySerializer.Serialize(writer, key);
				writer.WriteEndElement();

				writer.WriteStartElement(ValueTagName);
				TValue value = this[key];
				valueSerializer.Serialize(writer, value);
				writer.WriteEndElement();

				writer.WriteEndElement();
			}
		}
	}
}

Den ursprünglichen Code habe ich hier her.

Update:
Habe die Klasse mal um die Standardkonstruktoren der Basis-Klasse erweitert um ein bestehendes Dictionary einfach umwandeln zu können.

Löschen von Nodes in einem XML-Dokument

Ich hatte letztens ein XML-Dokument, welches ich etwas verschlanken wollte. Das Format war in etwa folgendes:

<root>
  <node1>Content</node1>
  <node2>Content</node2>
  <node3>Content</node3>
  <node4>Content</node4>
</root>

Ich wollte nur die „node1“ – Node behalten, sprich alle anderen entfernen. Daher mein erster Ansatz:

XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml("<root><node1>Content</node1><node2>Content</node2><node3>Content</node3><node4>Content</node4></root>");
XPathNavigator navigator = xmlDocument.CreateNavigator();
			
string[] nodesToRemove = new[] {"//node2", "//node3", "//node4"};

foreach (var nodeName in nodesToRemove)
{
	XPathNavigator node = navigator.SelectSingleNode(nodeName);
	if (node != null)
	{
		node.DeleteSelf();
	}
}

Console.WriteLine(xmlDocument.OuterXml);

Funktioniert und gut.

Nun wollte ich den Ansatz umdrehen, und alle Nodes entfernen, die nicht den Namen „node1“ haben.

XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml("<root><node1>Content</node1><node2>Content</node2><node3>Content</node3><node4>Content</node4></root>");
XPathNavigator navigator = xmlDocument.CreateNavigator();

string xpath = "/root/node()[name()!='node1']";

XPathNodeIterator nodes = navigator.Select(xpath);
while(nodes.MoveNext())
{
	if(nodes.Current != null)
	{
		nodes.Current.DeleteSelf();
	}
}

Console.WriteLine(xmlDocument.OuterXml);

Leider war das Ergebnis nicht so wie erwartet, obwohl der XPath korrekt ist und die anderen Nodes selektiert. Es wurde nur „node2“ gelöscht, sprich die erste Node, welche mit dem NodeIterator angesprochen wird. Durch das DeleteSelf() liefert MoveNext() automatisch false zurück, wenn es wieder aufgerufen wird. Ähnliche Probleme gibt es etwa auch, wenn in einer foreach()-Schleife der Enumerator geändert wird. Eine mögliche Lösung wäre folgendes:

while(nodes.MoveNext())
{
	if(nodes.Current != null)
	{
		nodes.Current.DeleteSelf();
		nodes = navigator.Select(xpath);
	}
}

Nach dem Löschen wird der XPath einfach noch mal ausgeführt. Meiner Meinung nach keine wirklich schöne Lösung, leider aber die einzige die ich gefunden habe, welche auch funktioniert.