فصل ۱۱: الگوهای XmlReader/XmlWriter، ترکیب با X-DOM و آغاز JSON
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
الگوهای XmlReader/XmlWriter — ادامه
بهترین رویکرد این نیست که یک متد بزرگ بنویسیم؛ بهتر است قابلیت XML را داخل خود typeهای Customer و Supplier با متدهای ReadXml و WriteXml کپسوله کنیم. الگو ساده است:
ReadXml و WriteXml هنگام خروج، reader/writer را در همان depth نگه میدارند؛ReadXml outer element را میخواند، اما WriteXml فقط inner content آن را مینویسد.
پیادهسازی Customer:
public class Customer
{
public const string XmlName = "customer";
public int? ID;
public string FirstName, LastName;
public Customer () { }
public Customer (XmlReader r) { ReadXml (r); }
public void ReadXml (XmlReader r)
{
if (r.MoveToAttribute ("id")) ID = r.ReadContentAsInt();
r.ReadStartElement();
FirstName = r.ReadElementContentAsString ("firstname", "");
LastName = r.ReadElementContentAsString ("lastname", "");
r.ReadEndElement();
}
public void WriteXml (XmlWriter w)
{
if (ID.HasValue) w.WriteAttributeString ("id", "", ID.ToString());
w.WriteElementString ("firstname", FirstName);
w.WriteElementString ("lastname", LastName);
}
}
توجه کنید ReadXml nodeهای outer start/end را میخواند. اگر caller این کار را انجام میداد، Customer نمیتوانست attributeهای خودش را بخواند. دلیل نامتقارن بودن WriteXml دو مورد است: caller ممکن است بخواهد نام outer element را خودش انتخاب کند؛ و ممکن است بخواهد attribute اضافی مانند subtype بنویسد تا هنگام خواندن برای انتخاب class مناسب استفاده شود.
کلاس Supplier مشابه است:
public class Supplier
{
public const string XmlName = "supplier";
public string Name;
public Supplier () { }
public Supplier (XmlReader r) { ReadXml (r); }
public void ReadXml (XmlReader r)
{
r.ReadStartElement();
Name = r.ReadElementContentAsString ("name", "");
r.ReadEndElement();
}
public void WriteXml (XmlWriter w) =>
w.WriteElementString ("name", Name);
}
در کلاس Contacts باید elementهای contacts را enumerate کنیم و بررسی کنیم هر subelement یک customer است یا supplier. همچنین باید دام empty element را پوشش دهیم:
public void ReadXml (XmlReader r)
{
bool isEmpty = r.IsEmptyElement;
r.ReadStartElement();
if (isEmpty) return;
while (r.NodeType == XmlNodeType.Element)
{
if (r.Name == Customer.XmlName) Customers.Add (new Customer (r));
else if (r.Name == Supplier.XmlName) Suppliers.Add (new Supplier (r));
else
throw new XmlException ("Unexpected node: " + r.Name);
}
r.ReadEndElement();
}
public void WriteXml (XmlWriter w)
{
foreach (Customer c in Customers)
{
w.WriteStartElement (Customer.XmlName);
c.WriteXml (w);
w.WriteEndElement();
}
foreach (Supplier s in Suppliers)
{
w.WriteStartElement (Supplier.XmlName);
s.WriteXml (w);
w.WriteEndElement();
}
}
Serialize کردن یک Contacts شامل Customer و Supplier به فایل XML:
var settings = new XmlWriterSettings();
settings.Indent = true;
using XmlWriter writer = XmlWriter.Create ("contacts.xml", settings);
var cts = new Contacts()
// Add Customers and Suppliers...
writer.WriteStartElement ("contacts");
cts.WriteXml (writer);
writer.WriteEndElement();
Deserialize از همان فایل:
var settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
settings.IgnoreComments = true;
settings.IgnoreProcessingInstructions = true;
using XmlReader reader = XmlReader.Create("contacts.xml", settings);
reader.MoveToContent();
var cts = new Contacts();
cts.ReadXml(reader);
ترکیب XmlReader/XmlWriter با X-DOM
در هر نقطهای از درخت XML که XmlReader/XmlWriter دستوپاگیر شود میتوانید X-DOM را وارد کنید. استفاده از X-DOM برای inner elementها، سادگی X-DOM را با مصرف حافظهٔ پایین XmlReader/XmlWriter ترکیب میکند.
استفاده از XmlReader با XElement
برای خواندن current element به X-DOM، XNode.ReadFrom را با XmlReader صدا میزنید. برخلاف XElement.Load greedy نیست و کل document را انتظار ندارد؛ فقط تا انتهای subtree جاری میخواند.
اگر log file زیر یک میلیون logentry داشته باشد، load کردن کل آن در X-DOM اتلاف حافظه است:
<log>
<logentry id="1">
<date>...</date>
<source>...</source>
...
</logentry>
...
</log>
راه بهتر: هر logentry را با XmlReader پیمایش و با XElement جداگانه پردازش کنید.
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = true;
using XmlReader r = XmlReader.Create ("logfile.xml", settings);
r.ReadStartElement ("log");
while (r.Name == "logentry")
{
XElement logEntry = (XElement) XNode.ReadFrom (r);
int id = (int) logEntry.Attribute ("id");
DateTime date = (DateTime) logEntry.Element ("date");
string source = (string) logEntry.Element ("source");
...
}
r.ReadEndElement();
اگر الگوی بخش قبل را دنبال کنید، میتوانید XElement را داخل ReadXml/WriteXml type سفارشی قرار دهید بدون اینکه caller بفهمد. بازنویسی Customer.ReadXml:
public void ReadXml (XmlReader r)
{
XElement x = (XElement) XNode.ReadFrom (r);
ID = (int) x.Attribute ("id");
FirstName = (string) x.Element ("firstname");
LastName = (string) x.Element ("lastname");
}
XElement با XmlReader همکاری میکند تا namespaceها حفظ و prefixها درست expand شوند، حتی اگر در سطح outer تعریف شده باشند. اگر root دارای default namespace باشد، XElementهای ساختهشده در سطح logentry همان outer namespace را درست به ارث میگیرند.
استفاده از XmlWriter با XElement
میتوان XElement را فقط برای نوشتن inner elementها در XmlWriter بهکار برد. کد زیر یک میلیون logentry را بدون نگهداشتن کل ساختار در حافظه مینویسد:
using XmlWriter w = XmlWriter.Create ("logfile.xml");
w.WriteStartElement ("log");
for (int i = 0; i < 1000000; i++)
{
XElement e = new XElement ("logentry",
new XAttribute ("id", i),
new XElement ("date", DateTime.Today.AddDays (-1)),
new XElement ("source", "test"));
e.WriteTo (w);
}
w.WriteEndElement ();
استفاده از XElement overhead اجرایی ناچیزی دارد. اگر مثال را طوری عوض کنیم که همهچیز فقط با XmlWriter نوشته شود، تفاوت قابلاندازهگیری در زمان اجرا وجود ندارد.
کار با JSON
JSON به یک جایگزین محبوب برای XML تبدیل شده است. اگرچه قابلیتهای پیشرفتهٔ XML مانند namespace، prefix و schema را ندارد، ساده و خلوت است و format آن شبیه چیزی است که از تبدیل یک object جاوااسکریپت به string انتظار دارید.
از نظر تاریخی .NET پشتیبانی built-in از JSON نداشت و باید به libraryهای third-party، عمدتاً Json.NET، تکیه میکردید. با اینکه این وضعیت دیگر برقرار نیست، Json.NET همچنان محبوب است؛ کتاب سه دلیل میآورد:
- از سال ۲۰۱۱ وجود داشته است؛
- همان API روی پلتفرمهای قدیمیتر .NET نیز اجرا میشود؛
- حداقل در گذشته، از APIهای JSON مایکروسافت functionalتر تلقی میشد.
APIهای JSON مایکروسافت از ابتدا برای سادگی و کارایی بسیار بالا طراحی شدهاند و از .NET 6 قابلیتهایشان به Json.NET بسیار نزدیک شده است.
این بخش موارد زیر را پوشش میدهد:
- reader/writer فقط رو به جلو:
Utf8JsonReader و Utf8JsonWriter؛ - DOM فقطخواندنی
JsonDocument؛ - DOM خواندنی/نوشتنی
JsonNode.
مکمل آنلاین کتاب JsonSerializer را نیز برای serialization/deserialization خودکار JSON به classها پوشش میدهد؛ نشانی اینترنتی آن در این نسخه فعال نشده است.
Utf8JsonReader
System.Text.Json.Utf8JsonReader یک reader بهینه، forward-only، برای متن JSON کدگذاریشده با UTF-8 است. از نظر مفهومی شبیه XmlReader است و تقریباً به همان شیوه استفاده میشود. ادامهٔ جزئیات در مقالهٔ بعدی آمده است.