2014-08-30 24 views
5

Tôi hiện đang viết một lớp bản đồ hai chiều, và tôi đang gặp một số rắc rối với việc tuần tự hóa/deserialization của lớp (câu hỏi ở dưới).Từ điển trống trên deserialization

Đây là các phần của lớp học có liên quan.

/// <summary> 
/// Represents a dictionary where both keys and values are unique, and the mapping between them is bidirectional. 
/// </summary> 
/// <typeparam name="TKey"> The type of the keys in the dictionary. </typeparam> 
/// <typeparam name="TValue"> The type of the values in the dictionary. </typeparam> 
[Serializable] 
public class BidirectionalDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IEquatable<BidirectionalDictionary<TKey, TValue>>, ISerializable, IDeserializationCallback 
{ 

     /// <summary> 
     /// A dictionary that maps the keys to values. 
     /// </summary> 
     private readonly Dictionary<TKey, TValue> forwardMap; 

     /// <summary> 
     /// A dictionary that maps the values to keys. 
     /// </summary> 
     private readonly Dictionary<TValue, TKey> inverseMap; 

     /// <summary> 
     /// An instance of the dictionary where the values are the keys, and the keys are the values. 
     /// </summary> 
     private readonly BidirectionalDictionary<TValue, TKey> inverseInstance; 

     /// <summary> 
     /// Initializes a new instance of the dictionary class with serialized data. </summary> 
     /// </summary> 
     /// <param name="info"> The serialization info. </param> 
     /// <param name="context"> The sserialization context. </param> 
     protected BidirectionalDictionary(SerializationInfo info, StreamingContext context) 
     { 
      this.forwardMap = (Dictionary<TKey, TValue>)info.GetValue("UnderlyingDictionary", typeof(Dictionary<TKey, TValue>)); 
      this.inverseMap = new Dictionary<TValue, TKey>(
       forwardMap.Count, 
       (IEqualityComparer<TValue>)info.GetValue("InverseComparer", typeof(IEqualityComparer<TValue>))); 

      // forwardMap is always empty at this point. 
      foreach (KeyValuePair<TKey, TValue> entry in forwardMap) 
       inverseMap.Add(entry.Value, entry.Key); 

      this.inverseInstance = new BidirectionalDictionary<TValue, TKey>(this); 
     } 

     /// <summary> 
     /// Gets the data needed to serialize the dictionary. 
     /// </summary> 
     /// <param name="info"> The serialization info. </param> 
     /// <param name="context"> The serialization context. </param> 
     public void GetObjectData(SerializationInfo info, StreamingContext context) 
     { 
      info.AddValue("UnderlyingDictionary", forwardMap); 
      info.AddValue("InverseComparer", inverseMap.Comparer); 
     } 
} 

Từ điển tiên tiến hơn và inverseMap chứa các dữ liệu chính xác cùng, ý tưởng của tôi là để chỉ serialize một trong số họ (forwardMap), và sau đó xây dựng khác (inverseMap) từ dữ liệu của nó trên deserialization. Tuy nhiên, inverseMap không nhận được dân cư với bất kỳ dữ liệu nào trong hàm tạo deserialization. Có vẻ như từ điển forwardMap chỉ hoàn toàn deserialized sau khi constructor deserialization của lớp đã thực hiện.

Bất kỳ ý tưởng nào về cách sửa lỗi này?

+0

chỉ để xác nhận, bạn đang sử dụng ['BinaryFormatter'] (http://msdn.microsoft.com/en-us/library/system.runtime.serialization.formatters.binary.binaryformatter%28v=vs.110% 29.aspx)? – dbc

+0

Bạn có thể vui lòng đăng toàn bộ mã để chúng tôi có thể kiểm tra trực tiếp không? –

+0

Đã sắp sửa chỉnh sửa và đăng toàn bộ mã, nhưng vấn đề chính xác là những gì @dbc được giải thích dưới đây. Vì vậy, yeah, tôi đang sử dụng BinaryFormatter. – Henrik

Trả lời

7

Tôi giả sử bạn đang sử dụng BinaryFormatter.

BinaryFormatter là bộ nối tiếp đồ thị. Thay vì các đối tượng được lưu trữ trong một cây thuần túy, chúng được gán các id đối tượng tạm thời và được lưu trữ khi chúng được gặp phải. Vì vậy, khi một đối tượng được deserialized, nó không được đảm bảo rằng tất cả các đối tượng tham chiếu đã được deserialized trước đó. Do đó, có thể các mục nhập trong số forwardMap của bạn chưa được điền.

Cách giải quyết thông thường là thêm IDeserializationCallback logic để lớp học của bạn, và xây dựng của bạn inverseMapinverseInstance sau khi tất cả mọi thứ đã được deserialized trong phương pháp OnDeserialization. Tuy nhiên, Dictionary<TKey, TValue> cũng thực hiện IDeserializationCallback, giới thiệu một vấn đề sắp xếp thứ tự bổ sung: nó không được đảm bảo để được gọi trước của bạn. Về chủ đề này, Microsoft writes:

Đối tượng được dựng lại từ trong ra ngoài, và gọi phương pháp trong deserialization có thể có tác dụng phụ không mong muốn, vì các phương pháp gọi là có thể tham khảo đối tượng tài liệu tham khảo chưa được deserialized bởi thời gian cuộc gọi được thực hiện. Nếu lớp được deserialized thực hiện IDeserializationCallback, phương thức OnSerialization sẽ tự động được gọi khi toàn bộ đồ thị đối tượng đã được deserialized. Tại thời điểm này, tất cả các đối tượng con được tham chiếu đã được khôi phục hoàn toàn. Bảng băm là một ví dụ điển hình của một lớp khó thực hiện mà không cần sử dụng trình xử lý sự kiện được mô tả ở trên. Thật dễ dàng để lấy các cặp khóa/giá trị trong quá trình deserialization, nhưng việc thêm các đối tượng này trở lại bảng băm có thể gây ra vấn đề vì không có sự đảm bảo rằng các lớp bắt nguồn từ bảng băm đã được deserialized. Phương pháp gọi trên bảng băm ở giai đoạn này do đó không được khuyến khích.

Như vậy có một vài điều bạn có thể làm:

  1. Thay vì lưu trữ một Dictionary<TKey,TValue>, lưu trữ một mảng của KeyValuePair<TKey,TValue>. Điều này có lợi thế là làm cho dữ liệu nhị phân của bạn đơn giản hơn nhưng yêu cầu bạn phân bổ mảng trong phương thức GetObjectData() của bạn.

  2. Hoặc làm theo những lời khuyên trong dictionary reference source:

    // It might be necessary to call OnDeserialization from a container if the container object also implements 
    // OnDeserialization. However, remoting will call OnDeserialization again. 
    // We can return immediately if this function is called twice. 
    // Note we set remove the serialization info from the table at the end of this method. 
    

    Tức làtrong callback của bạn, gọi phương thức OnDeserialization của từ điển lồng nhau của bạn trước khi sử dụng nó:

    public partial class BidirectionalDictionary<TKey, TValue> : IDeserializationCallback 
    { 
        public void OnDeserialization(object sender) 
        { 
         this.forwardMap.OnDeserialization(sender); 
         foreach (KeyValuePair<TKey, TValue> entry in forwardMap) 
         { 
          this.inverseMap.Add(entry.Value, entry.Key); 
         } 
         // inverseInstance will no longer be able to be read-only sicne it is being allocated in a post-deserialization callback. 
         this.inverseInstance = new BidirectionalDictionary<TValue, TKey>(this); 
        } 
    

    (. Bạn có thể làm điều đó trong một phương pháp [OnDeserialied] thay vì nếu bạn thích)

Ngẫu nhiên, this blog post tuyên bố rằng an toàn để gọi phương thức OnDeserialization của số HashTable từ hàm khởi tạo deserialization của một lớp có chứa, thay vì sau này từ OnDeserialization, vì vậy bạn có thể thử.

+0

Đây chính xác là nguyên nhân của sự cố. Tôi đã thực sự thử IDeserializationCallback approch, nhưng rõ ràng nó không hoạt động (vì từ điển cũng có gọi lại riêng của nó). Bây giờ tất cả có ý nghĩa. – Henrik

Các vấn đề liên quan