2010-08-17 20 views
18

Khi BinaryFormatter deserializes một dòng vào các đối tượng, nó xuất hiện để tạo ra các đối tượng mới mà không cần gọi các nhà thầu.BinaryFormatter.Deserialize tạo đối tượng mới như thế nào?

Làm cách nào để thực hiện việc này? Và tại sao? Có điều gì khác trong .NET thực hiện điều này không?

Dưới đây là một bản demo:

[Serializable] 
public class Car 
{ 
    public static int constructionCount = 0; 

    public Car() 
    { 
     constructionCount++; 
    } 
} 

public class Test 
{ 
    public static void Main(string[] args) 
    { 
     // Construct a car 
     Car car1 = new Car(); 

     // Serialize and then deserialize to create a second, identical car 
     MemoryStream stream = new MemoryStream(); 
     BinaryFormatter formatter = new BinaryFormatter(); 
     formatter.Serialize(stream, car1); 
     stream.Seek(0, SeekOrigin.Begin); 
     Car car2 = (Car)formatter.Deserialize(stream); 

     // Wait, what happened? 
     Console.WriteLine("Cars constructed: " + Car.constructionCount); 
     if (car2 != null && car2 != car1) 
     { 
      Console.WriteLine("But there are actually two."); 
     } 
    } 
} 

Output:

Cars constructed: 1
But there are actually two.

+0

Tốt câu hỏi. Để làm việc xung quanh điều này, bạn sẽ cần phải làm một số con trỏ/tham chiếu fixups trong deserialization, mà có thể là khó khăn hoặc thậm chí không thể. Lưu ý thực tế rằng 'xe mới' chỉ được gọi một lần. Bạn có thể muốn thử điều này trong 2 quy trình. – leppie

+0

bản sao có thể có của [DataContractSerializer không gọi hàm tạo của tôi?] (Http://stackoverflow.com/questions/1076730/datacontractserializer-doesnt-call-my-constructor) –

+2

Lưu ý: Câu hỏi khác mà tôi liên kết là về DataContractSerializer , nhưng lời giải thích là như nhau cho BinaryFormatter –

Trả lời

3

Có điều là, BinaryFormatter là không thực sự làm cho đối tượng cụ thể của bạn. Nó đặt một đồ thị đối tượng trở lại vào bộ nhớ. Đồ thị đối tượng cơ bản là biểu diễn của đối tượng của bạn trong bộ nhớ; điều này đã được tạo khi đối tượng được tuần tự hóa. Sau đó, các cuộc gọi deserialize về cơ bản chỉ cần gậy mà đồ thị trở lại trong bộ nhớ như là một đối tượng tại một con trỏ mở, và sau đó nó được đúc thành những gì nó thực sự là bởi mã. Nếu nó bị sai, thì một ngoại lệ sẽ bị ném.

Như ví dụ cụ thể của bạn, bạn chỉ thực sự xây dựng một chiếc xe; bạn chỉ đang tạo bản sao chính xác của chiếc xe đó. Khi bạn serialize nó ra vào dòng, bạn lưu trữ một bản sao nhị phân chính xác của nó. Khi bạn deserialize nó, bạn không phải xây dựng bất cứ điều gì. Nó chỉ dính vào biểu đồ trong bộ nhớ tại một số giá trị con trỏ như một đối tượng và cho phép bạn làm bất cứ điều gì bạn muốn với nó.

So sánh của bạn về car1! = Car2 là true vì vị trí con trỏ khác nhau, vì Ô tô là loại tham chiếu.

Tại sao? Thành thật mà nói, thật dễ dàng để chỉ cần kéo biểu diễn nhị phân, thay vì phải đi và kéo từng thuộc tính và tất cả.

Tôi không chắc liệu có bất kỳ điều gì khác trong .NET sử dụng quy trình tương tự này hay không; các ứng cử viên có khả năng nhất sẽ là bất cứ điều gì khác mà sử dụng nhị phân của đối tượng trong một số định dạng trong quá trình tuần tự hóa.

17

Có hai thứ gọi là hàm tạo (hoặc ít nhất là phải làm).

Một là dành một lượng bộ nhớ nhất định cho đối tượng và thực hiện tất cả các dịch vụ cần thiết để nó trở thành đối tượng cho phần còn lại của thế giới .NET (lưu ý một số lượng handwaving nhất định trong giải thích này).

Cách khác là đặt đối tượng vào trạng thái ban đầu hợp lệ, có lẽ dựa trên các tham số - đây là những gì mã thực tế trong hàm tạo sẽ thực hiện.

Deserialisation thực hiện tương tự như bước đầu tiên bằng cách gọi FormatterServices.GetUninitializedObject và sau đó thực hiện tương tự như bước thứ hai bằng cách đặt giá trị cho trường tương đương với giá trị được ghi lại trong quá trình tuần tự hóa (có thể yêu cầu deserialising các đối tượng khác để nói giá trị).

Bây giờ, trạng thái deserialisation được đưa các đối tượng vào có thể không tương ứng với điều đó có thể bởi bất kỳ nhà xây dựng. Tốt nhất là nó sẽ lãng phí (tất cả các giá trị được thiết lập bởi các nhà xây dựng sẽ được ghi đè) và tồi tệ hơn nó có thể nguy hiểm (constructor có một số tác dụng phụ). Nó cũng có thể chỉ là không thể (chỉ constructor là một trong đó có tham số - serialization không có cách nào để biết những gì các đối số để sử dụng).

Bạn có thể xem nó như là một loại hàm tạo đặc biệt chỉ được sử dụng bởi deserialisation (OO purists sẽ - và nên rùng mình với ý tưởng về một constructor không xây dựng, ý tôi là chỉ tương tự, nếu bạn biết C++ nghĩ về cách ghi đè new hoạt động như xa như bộ nhớ đi và bạn đã có một tương tự tốt hơn, mặc dù vẫn chỉ là một tương tự). Bây giờ, điều này có thể là một vấn đề trong một số trường hợp - có thể chúng tôi có readonly trường chỉ có thể được đặt bởi một nhà xây dựng hoặc có thể chúng tôi có các tác dụng phụ mà chúng tôi muốn xảy ra.

Giải pháp cho cả hai là ghi đè hành vi tuần tự hóa với ISerializable. Điều này sẽ nối tiếp dựa trên một cuộc gọi đến ISerializable.GetObjectData và sau đó gọi một hàm tạo cụ thể với các trường SerializationInfoStreamingContext để deserialise (cho biết hàm tạo thậm chí có thể là riêng tư - có nghĩa là hầu hết các mã khác thậm chí sẽ không nhìn thấy nó). Do đó nếu chúng ta có thể deserialise readonly lĩnh vực và có bất kỳ tác dụng phụ chúng tôi muốn (chúng tôi cũng có thể làm tất cả các cách thức của những thứ để kiểm soát chỉ là những gì được serialized và làm thế nào).

Nếu chúng ta chỉ quan tâm đến việc đảm bảo một số tác dụng phụ xảy ra khi deserialisation xảy ra khi xây dựng, chúng tôi có thể thực hiện IDeserializationCallback và chúng tôi sẽ có IDeserializationCallback.OnDeserialization được gọi là khi quá trình deserialisation hoàn tất.

Đối với những thứ khác thực hiện tương tự như vậy, có các dạng tuần tự hóa khác trong .NET nhưng đó là tất cả những gì tôi biết. Bạn có thể tự gọi FormatterServices.GetUninitializedObject nhưng chặn một trường hợp mà bạn có một đảm bảo mạnh mẽ rằng mã tiếp theo sẽ đặt đối tượng được tạo thành trạng thái hợp lệ (nghĩa là loại tình huống bạn đang ở trong khi deserialising một đối tượng từ dữ liệu được tạo ra bởi serialising giống nhau loại đối tượng) làm như vậy là đầy đủ và là cách tốt để tạo ra lỗi thực sự khó chẩn đoán.

+1

+1 - IDeserializationCallback là một ý tưởng tuyệt vời. Sử dụng nó để khởi tạo các trường riêng tư cần thiết, v.v. Giải quyết vấn đề của tôi! – womp