.NET'te bir nesneyi UTF-8 XML olarak serileştirme


112

Doğru nesne imhası, kısalık için kaldırıldı, ancak bir nesneyi bellekte UTF-8 olarak kodlamanın en basit yolu buysa şok olurum. Daha kolay bir yolu olmalı değil mi?

var serializer = new XmlSerializer(typeof(SomeSerializableObject));

var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);

serializer.Serialize(streamWriter, entry);

memoryStream.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(memoryStream, System.Text.Encoding.UTF8);
var utf8EncodedXml = streamReader.ReadToEnd();


1
Kafam karıştı ... varsayılan kodlama UTF-8 değil mi?
flq

@flq, evet, varsayılan UTF-8'dir, ancak tekrar bir dizge olarak okuduğu için utf8EncodedXmlUTF-16 da o kadar önemli değildir .
Jon Hanna

1
@Garry, Jon Skeet ve ben farklı sorulara cevap verdiğimiz için açıklayabilir misin? Nesnenin UTF-8 olarak serileştirilmesini mi istiyorsunuz, yoksa kendisini UTF-8 olarak bildiren ve dolayısıyla daha sonra UTF-8'de kodlandığında doğru bildirime sahip olacak bir XML dizesi mi istiyorsunuz? (bu durumda, en basit yol, hem UTF-8 hem de UTF-16 için geçerli olduğundan, bildirimde bulunmamaktır).
Jon Hanna

@Jon geri okuyor, sorumda belirsizlik var. Çoğunlukla hata ayıklama amacıyla bir dizeye çıktı verdim. Pratikte, büyük olasılıkla ya diske ya da HTTP üzerinden bayt akışı yapacağım, bu da cevabınızı sorunumla daha doğrudan alakalı hale getirir. Karşılaştığım temel sorun XML'de UTF-8'in bildirilmesiydi, ancak daha doğru olmak için bir dizgenin aracılığından kaçınmalıyım, böylece platforma bağlı olmak yerine gerçek UTF-8 bayt gönderebilir / kalıcı hale getirebilirim (sanırım) kodlayan.
Garry Shutler

Yanıtlar:


55

Kodunuz, tekrar bir dizeye okurken UTF-8'i belleğe almaz, bu nedenle artık UTF-8'de değil, UTF-16'ya geri döner (en iyisi dizeleri daha yüksek bir düzeyde dikkate almak en iyisidir. zorunlu olduğu durumlar dışında herhangi bir kodlama).

Gerçek UTF-8 sekizlisini elde etmek için şunları kullanabilirsiniz:

var serializer = new XmlSerializer(typeof(SomeSerializableObject));

var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);

serializer.Serialize(streamWriter, entry);

byte[] utf8EncodedXml = memoryStream.ToArray();

Bıraktığın emrinin aynısını bıraktım. Aşağıdakileri biraz tercih ediyorum (normal bertaraf bırakılarak):

var serializer = new XmlSerializer(typeof(SomeSerializableObject));
using(var memStm = new MemoryStream())
using(var  xw = XmlWriter.Create(memStm))
{
  serializer.Serialize(xw, entry);
  var utf8 = memStm.ToArray();
}

Bu, aşağı yukarı aynı miktarda karmaşıklık, ancak her aşamada başka bir şey yapmak için makul bir seçim olduğunu gösteriyor; bunlardan en acil olanı, bir dosyaya, TCP / IP'ye, bellekten başka bir yere serileştirmektir. akış, veritabanı, vb. Sonuçta, aslında o kadar ayrıntılı değil.


4
Ayrıca. BOM'u bastırmak istiyorsanız kullanabilirsiniz XmlWriter.Create(memoryStream, new XmlWriterSettings { Encoding = new UTF8Encoding(false) }).
ony

Birinin (benim gibi) Jon'un gösterdiği gibi oluşturulan XML'i okuması gerekiyorsa, bellek akışını 0 olarak yeniden konumlandırmayı unutmayın, aksi takdirde "Kök öğe eksik" diyen bir istisna alırsınız. Öyleyse şunu yapın: memStm.Position = 0; XmlReader xmlReader = XmlReader.Create (memStm)
Mishra

277

Hayır, StringWriterara maddeden kurtulmak için a kullanabilirsiniz MemoryStream. Ancak, bunu XML'e zorlamak StringWriteriçin Encodingözelliği geçersiz kılan a kullanmanız gerekir :

public class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

Veya henüz C # 6 kullanmıyorsanız:

public class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}

Sonra:

var serializer = new XmlSerializer(typeof(SomeSerializableObject));
string utf8;
using (StringWriter writer = new Utf8StringWriter())
{
    serializer.Serialize(writer, entry);
    utf8 = writer.ToString();
}

Açıkçası Utf8StringWriter, yapıcısında herhangi bir kodlamayı kabul eden daha genel bir sınıf yapabilirsiniz - ancak benim deneyimime göre UTF-8, a için en sık gereken "özel" kodlamadır StringWriter:)

Şimdi Jon Hanna'nın dediği gibi, bu yine de dahili olarak UTF-16 olacaktır, ancak muhtemelen bir noktada onu ikili veriye dönüştürmek için başka bir şeye aktaracaksınız ... bu noktada yukarıdaki dizeyi kullanabilirsiniz, bunu UTF-8 bayta dönüştürürseniz her şey iyi olacaktır - çünkü XML bildirimi kodlama olarak "utf-8" i belirtir.

DÜZENLEME: Bunun işe yaradığını göstermek için kısa ama eksiksiz bir örnek:

using System;
using System.Text;
using System.IO;
using System.Xml.Serialization;

public class Test
{    
    public int X { get; set; }

    static void Main()
    {
        Test t = new Test();
        var serializer = new XmlSerializer(typeof(Test));
        string utf8;
        using (StringWriter writer = new Utf8StringWriter())
        {
            serializer.Serialize(writer, t);
            utf8 = writer.ToString();
        }
        Console.WriteLine(utf8);
    }


    public class Utf8StringWriter : StringWriter
    {
        public override Encoding Encoding => Encoding.UTF8;
    }
}

Sonuç:

<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <X>0</X>
</Test>

İstediğimiz "utf-8" in beyan edilen kodlamasına dikkat edin, inanıyorum.


2
StringWriter üzerinde Encoding parametresini geçersiz kıldığınızda bile, yazılan verileri bir StringBuilder'a göndermeye devam eder, bu nedenle hala UTF-16'dır. Ve dize yalnızca UTF-16 olabilir.
Jon Hanna

4
@Jon: Denedin mi? Bende var ve işe yarıyor. Burada önemli olan beyan edilen kodlamadır; açık bir şekilde dahili olarak dize hala UTF-16'dır, ancak bu ikiliye dönüştürülene kadar herhangi bir fark yaratmaz (UTF-8 dahil herhangi bir kodlama kullanabilir). TextWriter.EncodingMülkiyet dokümanın içindeki belirtmek için hangi kodlama adı belirlemek için XML seri hale tarafından kullanılır.
Jon Skeet

2
@Jon: Bildirilen kodlama neydi? Tecrübelerime göre, bunun gibi soruların gerçekten yapmaya çalıştığı şey bu - kendisini UTF-8 olarak ilan eden bir XML belgesi oluşturun. Dediğiniz gibi, ihtiyacınız olana kadar metnin herhangi bir kodlamada olduğunu düşünmemek en iyisidir ... ama XML belgesi bir kodlama bildirdiği için, bu dikkate almanız gereken bir şeydir.
Jon Skeet

2
@Garry, şu anda düşünebildiğim en basit şey cevabımdaki ikinci örneği almak, ancak XmlWriterbunu bir XmlWriterSettingsnesneyi alan fabrika yöntemiyle oluşturduğunuzda ve OmitXmlDeclarationözelliğin ayarlı olması true.
Jon Hanna

4
+1 Utf8StringWriterÇözümünüz son derece güzel ve temiz
Adriano Carneiro

17

Kalıtım kullanarak çok iyi cevap, sadece başlatıcıyı geçersiz kılmayı unutmayın

public class Utf8StringWriter : StringWriter
{
    public Utf8StringWriter(StringBuilder sb) : base (sb)
    {
    }
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}

teşekkürler, bunu en zarif seçenek
buluyorum

5

Sorunu çok iyi açıklayan ve birkaç farklı çözümü tanımlayan bu blog gönderisini buldum:

(ölü bağlantı kaldırıldı)

Bunu yapmanın en iyi yolunun bellekteyken XML bildirimini tamamen çıkarmak olduğu fikrine karar verdim. Aslında öyle zaten o noktada UTF-16, ancak belirli bir kodlama ile bir dosyaya yazılır kadar XML bildirimi anlamlı görünmüyor; ve o zaman bile beyan gerekli değildir. En azından serileştirmeyi bozuyor gibi görünmüyor.

@Jon Hanna'nın bahsettiği gibi, bu şu şekilde oluşturulmuş bir XmlWriter ile yapılabilir:

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.