Các loại kế thừa và tuần tự hóa XML


84

Tiếp theo câu hỏi trước của tôi, tôi đang làm việc để làm cho mô hình đối tượng của mình được tuần tự hóa sang XML. Nhưng bây giờ tôi đã gặp phải một vấn đề (rất ngạc nhiên!).

Vấn đề tôi gặp phải là tôi có một bộ sưu tập, thuộc loại lớp cơ sở trừu tượng, được điền bởi các kiểu dẫn xuất cụ thể.

Tôi nghĩ rằng sẽ ổn nếu chỉ cần thêm các thuộc tính XML vào tất cả các lớp có liên quan và mọi thứ sẽ diễn ra suôn sẻ. Đáng buồn thay, đó không phải là trường hợp!

Vì vậy, tôi đã thực hiện một số đào trên Google và bây giờ tôi hiểu tại sao nó không hoạt động. Trong đó các XmlSerializerlà trong thực tế thực hiện một số phản ánh thông minh để sắp đặt từng đối tượng đến / từ XML, và vì nó dựa trên loại trừu tượng, nó không thể tìm ra cái quái gì nó đang nói chuyện với . Khỏe.

Tôi đã xem trang này trên CodeProject, có vẻ như nó có thể giúp ích rất nhiều (chưa đọc / tiêu thụ đầy đủ), nhưng tôi nghĩ tôi cũng muốn đưa vấn đề này vào bảng StackOverflow, để xem bạn có cách nào không hack / thủ thuật để thiết lập và chạy nó theo cách nhanh nhất / nhẹ nhất có thể.

Một điều tôi cũng nên nói thêm là tôi KHÔNG muốn đi xuống XmlIncludetuyến đường. Đơn giản là có quá nhiều mối liên hệ với nó, và khu vực này của hệ thống đang được phát triển nặng, vì vậy nó sẽ là một vấn đề thực sự về bảo trì!


1
Sẽ rất hữu ích nếu xem một số đoạn mã liên quan được trích xuất từ ​​các lớp bạn đang cố gắng tuần tự hóa.
Rex M

Mate: Tôi mở cửa trở lại bởi vì tôi cảm thấy người khác có thể thấy hữu ích này, nhưng cảm thấy tự do để đóng cửa nếu không đồng ý
JamesSugrue

Hơi bối rối vì điều này, vì đã không có gì trên chủ đề này quá lâu?
Rob Cooper

Câu trả lời:


54

Vấn đề đã được giải quyết!

OK, vì vậy cuối cùng tôi đã đến được đó (phải thừa nhận rằng có rất nhiều sự giúp đỡ từ đây !).

Vì vậy, tóm tắt:

Bàn thắng:

  • Tôi không muốn đi xuống tuyến đường XmlInclude do đau đầu về bảo trì.
  • Khi một giải pháp được tìm thấy, tôi muốn nó nhanh chóng được triển khai trong các ứng dụng khác.
  • Có thể sử dụng các bộ sưu tập các kiểu Trừu tượng, cũng như các thuộc tính trừu tượng riêng lẻ.
  • Tôi thực sự không muốn bận tâm đến việc phải làm những việc "đặc biệt" trong các lớp bê tông.

Các vấn đề đã xác định / Điểm cần lưu ý:

  • XmlSerializer thực hiện một số phản ánh khá thú vị, nhưng nó rất hạn chế khi nói đến các kiểu trừu tượng (tức là nó sẽ chỉ hoạt động với các thể hiện của chính kiểu trừu tượng, không phải các lớp con).
  • Các trình trang trí thuộc tính Xml xác định cách XmlSerializer xử lý các thuộc tính mà nó tìm thấy. Loại vật lý cũng có thể được chỉ định, nhưng điều này tạo ra sự kết hợp chặt chẽ giữa lớp và bộ nối tiếp (không tốt).
  • Chúng ta có thể triển khai XmlSerializer của riêng mình bằng cách tạo một lớp triển khai IXmlSerializable .

Giải pháp

Tôi đã tạo một lớp chung, trong đó bạn chỉ định kiểu chung là kiểu trừu tượng mà bạn sẽ làm việc. Điều này cung cấp cho lớp khả năng "dịch" giữa kiểu trừu tượng và kiểu cụ thể vì chúng ta có thể mã hóa quá trình đúc (tức là chúng ta có thể nhận được nhiều thông tin hơn XmlSerializer có thể).

Sau đó, tôi đã triển khai giao diện IXmlSerializable , giao diện này khá đơn giản, nhưng khi tuần tự hóa, chúng tôi cần đảm bảo rằng chúng tôi ghi loại lớp cụ thể vào XML, vì vậy chúng tôi có thể truyền nó trở lại khi hủy tuần tự hóa. Điều quan trọng cần lưu ý là nó phải có đầy đủ điều kiện vì các tập hợp mà hai lớp nằm trong có thể khác nhau. Tất nhiên có một chút kiểm tra kiểu và những thứ cần phải diễn ra ở đây.

Vì XmlSerializer không thể truyền, chúng tôi cần cung cấp mã để làm điều đó, vì vậy toán tử ngầm sau đó bị quá tải (tôi thậm chí chưa bao giờ biết bạn có thể làm điều này!).

Mã cho AbstractXmlSerializer là:

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

namespace Utility.Xml
{
    public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
    {
        // Override the Implicit Conversions Since the XmlSerializer
        // Casts to/from the required types implicitly.
        public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
        {
            return o.Data;
        }

        public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
        {
            return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
        }

        private AbstractType _data;
        /// <summary>
        /// [Concrete] Data to be stored/is stored as XML.
        /// </summary>
        public AbstractType Data
        {
            get { return _data; }
            set { _data = value; }
        }

        /// <summary>
        /// **DO NOT USE** This is only added to enable XML Serialization.
        /// </summary>
        /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
        public AbstractXmlSerializer()
        {
            // Default Ctor (Required for Xml Serialization - DO NOT USE)
        }

        /// <summary>
        /// Initialises the Serializer to work with the given data.
        /// </summary>
        /// <param name="data">Concrete Object of the AbstractType Specified.</param>
        public AbstractXmlSerializer(AbstractType data)
        {
            _data = data;
        }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null; // this is fine as schema is unknown.
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            // Cast the Data back from the Abstract Type.
            string typeAttrib = reader.GetAttribute("type");

            // Ensure the Type was Specified
            if (typeAttrib == null)
                throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because no 'type' attribute was specified in the XML.");

            Type type = Type.GetType(typeAttrib);

            // Check the Type is Found.
            if (type == null)
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the type specified in the XML was not found.");

            // Check the Type is a Subclass of the AbstractType.
            if (!type.IsSubclassOf(typeof(AbstractType)))
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the Type specified in the XML differs ('" + type.Name + "').");

            // Read the Data, Deserializing based on the (now known) concrete type.
            reader.ReadStartElement();
            this.Data = (AbstractType)new
                XmlSerializer(type).Deserialize(reader);
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            // Write the Type Name to the XML Element as an Attrib and Serialize
            Type type = _data.GetType();

            // BugFix: Assembly must be FQN since Types can/are external to current.
            writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            new XmlSerializer(type).Serialize(writer, _data);
        }

        #endregion
    }
}

Vì vậy, từ đó, làm cách nào để chúng ta yêu cầu XmlSerializer hoạt động với bộ tuần tự của chúng ta thay vì mặc định? Chúng ta phải chuyển kiểu của mình trong thuộc tính kiểu thuộc tính Xml, ví dụ:

[XmlRoot("ClassWithAbstractCollection")]
public class ClassWithAbstractCollection
{
    private List<AbstractType> _list;
    [XmlArray("ListItems")]
    [XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))]
    public List<AbstractType> List
    {
        get { return _list; }
        set { _list = value; }
    }

    private AbstractType _prop;
    [XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))]
    public AbstractType MyProperty
    {
        get { return _prop; }
        set { _prop = value; }
    }

    public ClassWithAbstractCollection()
    {
        _list = new List<AbstractType>();
    }
}

Ở đây bạn có thể thấy, chúng ta có một bộ sưu tập và một thuộc tính duy nhất đang được hiển thị và tất cả những gì chúng ta cần làm là thêm tham số có tên kiểu vào khai báo Xml, thật dễ dàng! : D

LƯU Ý: Nếu bạn sử dụng mã này, tôi thực sự đánh giá cao lời cảm ơn. Nó cũng sẽ giúp thu hút nhiều người hơn đến với cộng đồng :)

Bây giờ, nhưng không chắc phải làm gì với các câu trả lời ở đây vì tất cả họ đều có chuyên gia và phản ứng của mình. Tôi sẽ nâng cấp những thứ mà tôi cảm thấy hữu ích (không xúc phạm đến những thứ không có ích) và đóng nó lại khi tôi có đại diện :)

Vấn đề thú vị và niềm vui tốt để giải quyết! :)


Tôi đã tự mình gặp vấn đề này một thời gian trước đây. Về mặt cá nhân, tôi đã từ bỏ XmlSerializer và sử dụng trực tiếp giao diện IXmlSerializable, vì tất cả các lớp của tôi đều cần thiết để triển khai nó. Nếu không, các giải pháp là khá giống nhau. Viết tốt mặc dù :)
Thorarin.

Chúng tôi sử dụng thuộc tính XML_ nơi chúng tôi chuyển đổi danh sách thành Mảng :)
Arcturus

2
Bởi vì một phương thức khởi tạo không tham số là cần thiết để khởi tạo động lớp.
Silas Hansen

1
Xin chào! Tôi đã tìm kiếm một giải pháp như thế này khá lâu rồi. Tôi nghĩ rằng nó tuyệt vời! Mặc dù tôi không thể tìm ra cách sử dụng nó, bạn có phiền cho một ví dụ không? Bạn có đang tuần tự hóa lớp của mình hoặc danh sách, chứa các đối tượng của bạn không?
Daniel

1
Mã đẹp. Lưu ý rằng contructor không tham số có thể được khai báo privatehoặc protectedđể thực thi rằng nó không có sẵn cho các lớp khác.
tcovo

9

Một điều cần xem xét là thực tế là trong phương thức khởi tạo XmlSerialiser, bạn có thể chuyển một mảng các kiểu mà trình nối tiếp có thể gặp khó khăn khi giải quyết. Tôi đã phải sử dụng điều đó khá nhiều lần khi một bộ sưu tập hoặc một bộ cấu trúc dữ liệu phức tạp cần được tuần tự hóa và những loại đó nằm trong các tập hợp khác nhau, v.v.

XmlSerialiser Constructor với extraTypes param

CHỈNH SỬA: Tôi muốn nói thêm rằng cách tiếp cận này có lợi ích hơn các thuộc tính XmlInclude, v.v. mà bạn có thể tìm ra cách khám phá và biên soạn danh sách các loại cụ thể có thể có của bạn trong thời gian chạy và đưa chúng vào.


Đây là những gì tôi đang cố gắng làm, nhưng nó không dễ dàng như tôi nghĩ: stackoverflow.com/questions/3897818/…
Luca

Đây là một bài đăng rất cũ nhưng đối với bất kỳ ai đang tìm cách thực hiện điều này như chúng tôi đã làm, xin lưu ý rằng hàm tạo của XmlSerializer với extraTypes param không lưu vào bộ nhớ cache các hợp ngữ mà nó tạo ra khi đang bay. Điều này khiến chúng tôi tốn hàng tuần để gỡ lỗi bộ nhớ bị rò rỉ. Vì vậy, nếu bạn định sử dụng các loại bổ sung với mã của câu trả lời được chấp nhận, hãy lưu vào bộ đệm nối tiếp . Hành vi này được ghi lại tại đây: support.microsoft.com/en-us/kb/886385
Julien Lebot 22/09/2016

3

Nghiêm túc mà nói, một khung công tác có thể mở rộng của POCO sẽ không bao giờ tuần tự hóa sang XML một cách đáng tin cậy. Tôi nói điều này bởi vì tôi có thể đảm bảo sẽ có ai đó đi cùng, mở rộng lớp học của bạn và thúc đẩy nó.

Bạn nên xem xét việc sử dụng XAML để tuần tự hóa các đồ thị đối tượng của mình. Nó được thiết kế để làm điều này, trong khi tuần tự hóa XML thì không.

Bộ tuần tự hóa và bộ giải mã Xaml xử lý các thông số chung mà không gặp sự cố, các bộ sưu tập các lớp cơ sở và giao diện cũng như (miễn là các bộ sưu tập tự triển khai IListhoặc IDictionary). Có một số lưu ý, chẳng hạn như đánh dấu thuộc tính bộ sưu tập chỉ đọc của bạn bằng dấu DesignerSerializationAttribute, nhưng việc làm lại mã của bạn để xử lý các trường hợp góc này không khó lắm.


Liên kết có vẻ là đã chết
bkribbs

Ồ, tốt. Tôi sẽ ném nó một chút. Nhiều tài nguyên khác về chủ đề này.

2

Chỉ cần một cập nhật nhanh chóng về điều này, tôi đã không quên!

Chỉ cần thực hiện thêm một số nghiên cứu, có vẻ như tôi đã chiến thắng, chỉ cần sắp xếp mã.

Cho đến nay, tôi có những điều sau:

  • Các XmlSeralizer về cơ bản là một lớp học mà hiện một số phản ánh tiện lợi trên lớp nó được serializing. Nó xác định các thuộc tính được tuần tự hóa dựa trên Loại .
  • Lý do sự cố xảy ra là do một kiểu không khớp đang xảy ra, nó đang mong đợi BaseType nhưng thực tế lại nhận DerivedType .. Mặc dù bạn có thể nghĩ rằng nó sẽ xử lý nó một cách đa hình, nhưng không phải vì nó sẽ liên quan đến toàn bộ tải phản xạ và kiểm tra kiểu, mà nó không được thiết kế để làm.

Hành vi này dường như có thể bị ghi đè (mã đang chờ xử lý) bằng cách tạo ra một lớp proxy để hoạt động như một trung gian cho bộ tuần tự. Điều này về cơ bản sẽ xác định loại của lớp dẫn xuất và sau đó tuần tự hóa nó như bình thường. Sau đó, lớp proxy này sẽ cung cấp dữ liệu XML đó sao lưu dòng tới bộ tuần tự chính ..

Xem không gian này! ^ _ ^


2

Đó chắc chắn là một giải pháp cho vấn đề của bạn, nhưng có một vấn đề khác, điều này phần nào làm suy yếu ý định sử dụng định dạng XML "di động" của bạn. Điều tồi tệ xảy ra khi bạn quyết định thay đổi các lớp trong phiên bản tiếp theo của chương trình và bạn cần hỗ trợ cả hai định dạng tuần tự hóa - định dạng mới và định dạng cũ (vì khách hàng của bạn vẫn sử dụng các tệp / cơ sở dữ liệu cũ của họ hoặc chúng kết nối với máy chủ của bạn sử dụng phiên bản cũ của sản phẩm của bạn). Nhưng bạn không thể sử dụng trình tuần tự này nữa, vì bạn đã sử dụng

type.AssemblyQualifiedName

trông giống như

TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089

chứa các thuộc tính lắp ráp và phiên bản của bạn ...

Bây giờ nếu bạn cố gắng thay đổi phiên bản lắp ráp của mình hoặc bạn quyết định ký nó, thì quá trình giải mã này sẽ không hoạt động ...


1

Tôi đã làm những điều tương tự như thế này. Những gì tôi thường làm là đảm bảo tất cả các thuộc tính tuần tự hóa XML đều nằm trên lớp cụ thể và chỉ cần các thuộc tính trên lớp đó gọi đến các lớp cơ sở (nếu cần) để truy xuất thông tin sẽ được hủy / tuần tự hóa khi trình tuần tự hóa gọi các thuộc tính đó. Đó là công việc mã hóa nhiều hơn một chút, nhưng nó hoạt động tốt hơn nhiều so với việc cố gắng buộc bộ tuần tự làm đúng.


1

Thậm chí tốt hơn, sử dụng ký hiệu:

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}

2
Điều này thật tuyệt nếu bạn biết các lớp học của mình, đó là cách lắng nghe thanh lịch nhất. Nếu bạn tải các lớp kế thừa mới từ một nguồn bên ngoài thì rất tiếc bạn không thể sử dụng nó.
Vladimir
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.