Truyền object: serialization / deserialization

    0

    Trong bài học này chúng ta sẽ xem xét khái niệm và các kỹ thuật trình tự hóa dữ liệu (object serialization) nhằm chuyển đổi object về dạng trung gian phục vụ cho việc truyền dữ liệu qua mạng.

    Trong tất cả các bài tập ví dụ từ đầu đến giờ chúng ta đều chỉ truyền đi các chuỗi ký tự. Tuy nhiên, trong hầu hết các project, chúng ta gặp phải một vấn đề khác: truyền một object hoặc danh sách các object từ tiến trình này tới tiến trình khác.

    Để thực hiện việc truyền các object phức tạp, chúng ta phải sử dụng đến các công cụ giúp trình tự hóa object (object serialization).

    Khái niệm Serialization

    Quá trình chuyển đổi một object về dạng trung gian để lưu trữ hoặc truyền thông được gọi là trình tự hóa dữ liệu (serialization); Quá trình khôi phục lại object từ dạng trung gian được gọi là giải trình tự hóa (deserialization).

    Trong lập trình socket, serialization là giai đoạn chuẩn bị dữ liệu trước khi gọi tới dịch vụ truyền thông mạng.

    Do môi trường truyền thông làm việc với hai loại dữ liệu chính là văn bản và mảng byte, quá trình serialization có thể xem là chuyển đổi object về một mảng byte hoặc về một chuỗi văn bản.

    Trường hợp chuyển object về mảng byte được gọi là trình tự hóa nhị phân (binary serialization). Việc chuyển object về chuỗi ký tự được gọi là trình tự hóa văn bản (text serialization).

    Yêu cầu cơ bản nhất của việc chuyển đổi này là phải đảm bảo thực hiện được việc khôi phục lại object từ dạng trung gian.

    Serialization và stream thường hoạt động cùng nhau. Trong đó, serialization sử dụng stream để đọc/ghi dữ liệu.

    Text serialization

    Ví dụ

    Hãy cùng thực hiện ví dụ sau: Giả sử có một class Student với các trường Id (int), FirstName (string), LastName (string), DateOfBirth (DateTime). Hãy truyền một object của Student từ client đến server.

    Tạo cấu trúc solution và project

    Bước 1. Tạo một solution trống đặt tên là TextSerialization.

    Bước 2. Tạo mới ba project trong solution này: Client (Console App), Server (Console App), Common (Class Library (.NET framework)).

    Bước 3. Thiết lập Multiple startup projects cho Client và Server.

    Bước 4. Thêm hai file mã nguồn Student.cs và TextSerializer.cs vào Common

    Bước 5. Thiết lập cho Client tham chiếu tới thư viện Common.

    Text serialization example reference
    Text serialization example reference

    Tương tự thiết lập để Server cùng tham chiếu tới thư viện Common

    Viết code cho thư viện Common

    using System;
    namespace Common
    {
        public class Student
        {
            public int Id { get; set; } = 1;
            public string FirstName { get; set; } = "";
            public string LastName { get; set; } = "";
            public DateTime DateOfBirth { get; set; } = DateTime.Now;
        }
    }
    using System;
    using System.Collections.Generic;
    namespace Common
    {
        public class TextSerializer
        {
            // Chuyển đổi một object của Student sang chuỗi ký tự. 
            // Chuỗi kết quả có hình thức tương tự chuỗi tham số của Http get.
            public static string Serialize(Student obj)
            {
                return $"Id = {obj.Id} " +
                    $"& FirstName = {obj.FirstName} " +
                    $"& LastName = {obj.LastName} " +
                    $"& DateOfBirth = {obj.DateOfBirth.Ticks}";
            }
            // Chuyển đổi một chuỗi trở lại thành object kiểu Student
            public static Student Deserialize(string data)
            {
                var dict = new Dictionary<string, string>();
                var pairs = data.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var pair in pairs)
                {
                    var p = pair.Split('='); // cắt mỗi phần tử lấy mốc là ký tự =
                    if (p.Length == 2) // một cặp khóa = giá_trị đúng sau khi cắt sẽ phải có 2 phần
                    {
                        var key = p[0].Trim(); // phần tử thứ nhất là khóa
                        var value = p[1].Trim(); // phần tử thứ hai là giá trị
                        dict[key] = value; // lưu cặp khóa-giá trị này lại sử dụng phép toán indexing                    
                    }
                }
                var obj = new Student();
                if (dict.ContainsKey("Id"))
                {
                    obj.Id = int.Parse(dict["Id"]);
                }
                if (dict.ContainsKey("FirstName"))
                {
                    obj.FirstName = dict["FirstName"];
                }
                if (dict.ContainsKey("LastName"))
                {
                    obj.LastName = dict["LastName"];
                }
                if (dict.ContainsKey("DateOfBirth"))
                {
                    obj.DateOfBirth = new DateTime(long.Parse(dict["DateOfBirth"]));
                }
                return obj;
            }
        }
    }

    Viết code cho Client và Server

    using Common;
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Title = "Client";
                while (true)
                {
                    Console.Write("Press enter to send ... ");
                    Console.ReadLine();
                    var student = new Student
                    {
                        Id = 1,
                        FirstName = "Nguyen Van",
                        LastName = "A",
                        DateOfBirth = new DateTime(1990, 12, 30)
                    };
                    var client = new TcpClient();
                    client.Connect(IPAddress.Loopback, 1308);
                    var stream = client.GetStream();
                    var writer = new StreamWriter(stream) { AutoFlush = true };
                    writer.WriteLine(TextSerializer.Serialize(student));
                }
            }
        }
    }
    using Common;
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    namespace Server
    {
        internal class Program
        {
            private static void Main()
            {
                Console.Title = "Server";
                var listener = new TcpListener(IPAddress.Any, 1308);
                listener.Start(10);
                while (true)
                {
                    var client = listener.AcceptTcpClient();
                    var stream = client.GetStream();
                    var reader = new StreamReader(stream);
                    var data = reader.ReadLine();
                    var student = TextSerializer.Deserialize(data);
                    Console.WriteLine($"Raw data:\r\n{data}\r\n");
                    Console.WriteLine("Deserialized object:");
                    Console.WriteLine($"Id: {student.Id}\r\nFirst Name: {student.FirstName}\r\nLast Name: {student.LastName}\r\nDate of birth: {student.DateOfBirth.ToShortDateString()}");
                    client.Close();
                }
            }
        }
    }
    Text serialization example

    Phân tích ví dụ

    Trong ví dụ trên chúng ta xây dựng một lớp thực thể Student, lớp TextSerializer có nhiệm vụ chuyển đổi object của Student thành chuỗi ký tự và ngược lại.

    ToString và Parse

    Như chúng ta thấy, quá trình chuyển đổi object của Student thành chuỗi ký tự, và chuyển đổi chuỗi ký tự thành object của Student là khá đơn giản. Hầu hết các kiểu dữ liệu cơ bản của C# đều hỗ trợ sẵn phương thức ToString (kế thừa từ lớp Object) để chuyển thành chuỗi ký tự, và phương thức Parse để chuyển đổi từ chuỗi ký tự.

    Điểm khác biệt ở đây là kiểu dữ liệu DateTime dùng để lưu trữ thông tin thời gian, bao gồm đầy đủ ngày, tháng, năm, giờ, phút, giây, mili giây. Biến của DateTime là một object bình thường (tương tự như biến của Student). Chúng ta lợi dụng thuộc tính Ticks của lớp này để biến đổi dữ liệu.

    Một tick biểu diễn khoảng thời gian bằng 100 nano giây (10 phần triệu của 1 giây), tức là 10 triệu ticks bằng 1 giây. Giá trị của thuộc tính Ticks là số tick tính từ 12h đêm ngày 1 tháng 1 năm 0001. Toàn bộ các tính toán trên thời gian của kiểu DateTime dựa trên con số này. Vì vậy, giá trị Ticks có thể sử dụng để tạo ra object của DateTime.

    Kết quả của text serialization trong ví dụ trên là chuỗi ký tự: "d = 1 & FirstName = Nguyen & LastName = Van A & DateOfBirth = 627981120000000000"

    Chuỗi ký tự này được viết theo định dạng tương tự như cách viết chuỗi tham số get của http.

    Một số lưu ý

    Chúng ta thấy, để thực hiện text serialization có những điểm đáng lưu ý:

    • vận dụng các yếu tố khác nhau của các kiểu dữ liệu để đảm bảo quá trình ngược (deserialization) có thể thực hiện được: ví dụ, lợi dụng thuộc tính Ticks ở trên;
    • đảm bảo phân tách được các phần của dữ liệu trong chuỗi để thực hiện quá trình ngược: trong ví dụ trên sử dụng ký tự & để phân tách các cặp khóa – giá trị, mỗi cặp khóa – giá trị lại dùng dấu = để phân chia;
    • có thể kết hợp tên của biến với dữ liệu để không phụ thuộc vào trật tự của dữ liệu trong chuỗi.

    Giải pháp ở trên chúng ta đưa ra chỉ là một trong số rất nhiều giải pháp khác nhau có thể áp dụng để chuyển đổi object thành chuỗi ký tự.

    Binary serialization

    Ví dụ

    Chúng ta làm một ví dụ khác tương tự với một solution mới đặt tên là BinarySerialization. Tất cả các thiết lập đều giống như ví dụ trên.

    Binary serialization example solution

    Viết code cho thư viện Common

    using System;
    namespace Common
    {
        public class Student
        {
            public int Id { get; set; } = 1;
            public string FirstName { get; set; } = "";
            public string LastName { get; set; } = "";
            public DateTime DateOfBirth { get; set; } = DateTime.Now;
        }
    }
    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace Common
    {
        public class BinarySerializer
        {
            // hai phương thức sau đây sử dụng lớp BitConverter.
            // BitConverter giúp chuyển đổi một biến (thuộc các kiểu cơ sở, trừ kiểu string)
            // về mảng byte.
            public static byte[] Serialize(Student obj)
            {
                // danh sách này sẽ chứa các byte thu từ từng trường dữ liệu
                var data = new List<byte>();
                // chuyển Id thành mảng byte và copy vào data
                data.AddRange(BitConverter.GetBytes(obj.Id));
                // đếm số byte của FirtName, chuyển thành mảng byte và copy vào data
                data.AddRange(BitConverter.GetBytes(Encoding.UTF8.GetByteCount(obj.FirstName)));
                // chuyển FirstName thành mảng byte và copy vào data
                data.AddRange(Encoding.UTF8.GetBytes(obj.FirstName));
                data.AddRange(BitConverter.GetBytes(Encoding.UTF8.GetByteCount(obj.LastName)));
                data.AddRange(Encoding.UTF8.GetBytes(obj.LastName));
                // chuyển DateOfBirth thành long (số Ticks), chuyển thành mảng byte,
                // và copy vào data
                data.AddRange(BitConverter.GetBytes(obj.DateOfBirth.Ticks));
                // chuyển đổi List thành Array
                return data.ToArray();
            }
            public static Student Deserialize(byte[] data)
            {
                var obj = new Student();
                int offset = 0;
                obj.Id = BitConverter.ToInt32(data, offset);
                offset += 4;
                var length1 = BitConverter.ToInt32(data, offset);
                offset += 4;
                obj.FirstName = Encoding.UTF8.GetString(data, offset, length1);
                offset += length1;
                var length2 = BitConverter.ToInt32(data, offset);
                offset += 4;
                obj.LastName = Encoding.UTF8.GetString(data, offset, length2);
                offset += length2;
                obj.DateOfBirth = new DateTime(BitConverter.ToInt64(data, offset));
                return obj;
            }
        }
    }

    Viết code cho Client và Server

    using Common;
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Title = "Client";
                while (true)
                {
                    Console.Write("Press enter to send ... ");
                    Console.ReadLine();
                    var student = new Student
                    {
                        Id = 1,
                        FirstName = "Nguyen Van",
                        LastName = "A",
                        DateOfBirth = new DateTime(1990, 12, 30)
                    };
                    var client = new TcpClient();
                    client.Connect(IPAddress.Loopback, 1308);
                    var stream = client.GetStream();
                    var writer = new BinaryWriter(stream);
                    var data = BinarySerializer.Serialize(student);
                    writer.Write(data.Length);
                    stream.Write(data, 0, data.Length);
                    stream.Flush();
                    client.Close();
                }
            }
        }
    }
    
    using Common;
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    namespace Server
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Title = "Server";
                var listener = new TcpListener(IPAddress.Any, 1308);
                listener.Start(10);
                while (true)
                {
                    var client = listener.AcceptTcpClient();
                    var stream = client.GetStream();
                    var reader = new BinaryReader(stream);
                    var length = reader.ReadInt32();
                    var data = reader.ReadBytes(length);
                    var student = BinarySerializer.Deserialize(data);
                    client.Close();
                    Console.WriteLine("Raw byte array:");
                    foreach (var b in data)
                        Console.Write($"{b} ");
                    Console.WriteLine("\r\nDeserialized object:");
                    Console.WriteLine($"Id: {student.Id}\r\nFirst Name: {student.FirstName}\r\nLast Name: {student.LastName}\r\nDate of birth: {student.DateOfBirth.ToShortDateString()}");
                }
            }
        }
    }
    Binary serialization example
    Binary serialization example

    Phân tích ví dụ

    Trong ví dụ trên chúng ta xây dựng lớp BinarySerializer chuyển đổi object của Student thành mảng byte và ngược lại. Trong binary serialization mỗi trường dữ liệu được biến đổi thành một mảng byte.

    Chúng ta kết hợp hai lớp BitConverter và Encoding để thực hiện binary serialization. Nếu trong class chứa các biến thuộc kiểu object khác, chúng ta lại cần tiếp tục áp dụng hai class này cho từng trường trong object đó.

    Các lớp hỗ trợ nói trên hoạt động rất tốt với các trường dữ liệu có kích thước cố định (như kiểu số, logic, char). Đối với các trường kiểu string có kích thước biến đổi thường phải bổ sung thêm thông tin về độ dài của chuỗi để quá trình deserialization có thể lấy ra đúng số byte của chuỗi đó.

    Cấu trúc chuỗi byte

    Đối với binary serialization, trật tự mã hóa các trường rất quan trọng. Mã hóa các trường theo thứ tự nào thì phải giải mã theo đúng vị trí của trường đó.

    Trong ví dụ trên chúng ta mã hóa theo trật tự: Id (4 byte, cố định), length 1 (4 byte, cố định, chứa thông tin độ dài của trường FirstName), FirstName (không cố định), length2 (4 byte, cố định, chứa thông tin độ dài của trường LastName), LastName (không cố định), DateOfBirth (8 byte, cố định).

    Trật tự byte của binary serialization

    Do đó, khi giải mã ta dùng một biến offset để định vị byte bắt đầu của mỗi thành phần:

    • Id: offset = 0, độ dài: 4 byte;
    • length1: offset = 4, độ dài: 4 byte;
    • FirstName: offset = 4 + 4, độ dài: length1;
    • length2: offset = 4 + 4 + length1, độ dài: 4 byte;
    • LastName: offset = 4 + 4 + length1 + 4, độ dài: length2;
    • DateOfBirth: offset = 4 + 4 + length1 + 4 + length2, độ dài: 8 byte.

    Kết quả chuyển đổi

    Trong ví dụ trên, kết quả của binary serialization là một chuỗi byte (31 byte): 1 0 0 0 6 0 0 0 78 103 117 121 101 110 5 0 0 0 86 97 110 32 65 0 128 64 95 128 9 183 8. Trong đó:

    • 1 0 0 0: giá trị Id (4 byte đầu tiên);
    • 6 0 0 0: độ dài của chuỗi FirstName (4 byte tiếp theo); con số này cho biết rằng 6 byte tiếp theo là phần dữ liệu của FirstName;
    • 78 103 117 121 101 110: giá trị số của FirstName khi mã bằng utf8 (6 byte tiếp);
    • 5 0 0 0: độ dài của chuỗi LastName (4 byte tiếp); con số này cho biết 5 byte tiếp theo là phần dữ liệu của LastName;
    • 86 97 110 32 65: giá trị số của LastName (5 byte tiếp);
    • 0 128 64 95 128 9 183 8: giá trị số của DateOfBirth (kiểu long, 8 byte)

    Một số lưu ý

    Binary serialization thường cho ra kết quả gọn nhẹ hơn nhưng không quá phù hợp nếu trong class có nhiều trường kích thước biến đổi (chủ yếu là string).

    Text serialization cho ra kết quả dài hơn nhưng dễ đọc (với người) và phù hợp nếu class chứa nhiều trường có độ dài biến đổi.

    Serialization và Stream thường song hành với nhau. Ở trên chúng ta minh họa de/serialization bằng cách chuyển đổi trực tiếp sang chuỗi hoặc mảng byte.

    Trên thực tế, quá trình trên thường làm việc với một luồng dữ liệu nào đó để tránh phải lưu trữ những mảng dữ liệu quá lớn trong chương trình. Quá trình serialization ghi dữ liệu thẳng vào stream, quá trình deserialization đọc thẳng dữ liệu từ stream

    Hỗ trợ serialization trong .NET

    Trong phần ví dụ về serialization chúng ta đã thấy việc chuyển một object về chuỗi ký tự hoặc mảng byte là một công việc tương đối phức tạp, tốn công sức và dễ sai sót, đặc biệt đối với các class lớn có nhiều trường dữ liệu, cũng như khi phải làm việc với nhiều class khác nhau.

    Để hỗ trợ cho người lập trình, .NET framework cung cấp các class hỗ trợ cho 3 loại serialization: binary, xml và json.

    BinaryFormatter

    Lớp BinaryFormatter: biến đổi một object về mảng byte và ghi trực tiếp vào một stream; đọc các byte dữ liệu từ một stream và biến đổi về object. Lớp BinaryFormatter nằm trong không gian tên System.Runtime.Serialization.Formatters.Binary.

    Hãy cùng thực hiện một ví dụ để xem cách sử dụng của BinaryFormatter

    Xây dựng cấu trúc solution

    Lặp lại các bước như ở ví dụ đầu tiên với text serialization để tạo ra solution TcpBinaryFormatter có cấu trúc như sau:

    Lưu ý: thiết lập để Client và Server cùng tham chiếu tới thư viện Common

    Viết code

    using System;
    namespace Common
    {
        [Serializable]
        public class Student
        {
            public int Id { get; set; } = 1;
            public string FirstName { get; set; } = "";
            public string LastName { get; set; } = "";
            public DateTime DateOfBirth { get; set; } = DateTime.Now;
        }
    }
    using Common;
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.Serialization.Formatters.Binary;
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Title = "Client";
                while (true)
                {
                    Console.Write("Press enter to send ... ");
                    Console.ReadLine();
                    var student = new Student
                    {
                        Id = 1,
                        FirstName = "Nguyen Van",
                        LastName = "A",
                        DateOfBirth = new DateTime(1990, 12, 30)
                    };
                    var client = new TcpClient();
                    client.Connect(IPAddress.Loopback, 1308);
                    var stream = client.GetStream();
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(stream, student);
                    client.Close();
                }
            }
        }
    }
    using Common;
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Runtime.Serialization.Formatters.Binary;
    namespace Server
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Title = "Server";
                var listener = new TcpListener(IPAddress.Any, 1308);
                listener.Start(10);
                while (true)
                {
                    var client = listener.AcceptTcpClient();
                    var stream = client.GetStream();
                    var formatter = new BinaryFormatter();
                    var student = formatter.Deserialize(stream) as Student;
                    client.Close();
                    Console.WriteLine("Deserialized object:");
                    Console.WriteLine($"Id: {student.Id}\r\nFirst Name: {student.FirstName}\r\nLast Name: {student.LastName}\r\nDate of birth: {student.DateOfBirth.ToShortDateString()}");
                }
            }
        }
    }

    XmlSerializer

    Lớp XmlSerializer: tương tự như BinaryFormatter, XmlSerializer biến đổi một object về dạng xml và ghi vào một stream, cũng như đọc một file xml và biến đổi về object. XmlSerializer nằm trong không gian tên System.Xml.Serialization.

    Do làm việc với xml là một dạng dữ liệu cấp cao, XmlSerializer cũng có thể dùng đến (nhưng không bắt buộc) hai lớp adapter XmlReader XmlWriter để để hỗ trợ đọc ghi xml.

    Hãy cùng thực hiện ví dụ sau

    using Common;
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Xml.Serialization;
    namespace Client
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Title = "Client";
                while (true)
                {
                    Console.Write("Press enter to send ... ");
                    Console.ReadLine();
                    var student = new Student
                    {
                        Id = 1,
                        FirstName = "Nguyen Van",
                        LastName = "A",
                        DateOfBirth = new DateTime(1990, 12, 30)
                    };
                    var client = new TcpClient();
                    client.Connect(IPAddress.Loopback, 1308);
                    var stream = client.GetStream();
                    XmlSerializer serializer = new XmlSerializer(typeof(Student));                
                    serializer.Serialize(stream, student);
                    client.Close();
                }
            }
        }
    }
    
    using Common;
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Xml.Serialization;
    namespace Server
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Title = "Server";
                var listener = new TcpListener(IPAddress.Any, 1308);
                listener.Start(10);
                while (true)
                {
                    var client = listener.AcceptTcpClient();
                    var stream = client.GetStream();
                    var serializer = new XmlSerializer(typeof(Student));
                    var student = serializer.Deserialize(stream) as Student;
                    client.Close();
                    Console.WriteLine("Deserialized object:");
                    Console.WriteLine($"Id: {student.Id}\r\nFirst Name: {student.FirstName}\r\nLast Name: {student.LastName}\r\nDate of birth: {student.DateOfBirth.ToShortDateString()}");
                }
            }
        }
    }
    

    NewtonSoft.Json

    Đối với định dạng json, mặc dù .NET framework có class hỗ trợ nhưng không thực sự tốt. Bộ thư viện NewtonSoft.Json thường được sử dụng rất nhiều hiện nay. Có thể download thư viện này từ NuGet. Xem bài viết này để biết cách cài đặt thư viện NewtonSoft.Json từ NuGet.

    Tạo solution TcpJsonSerializer tương tự như các ví dụ trên và cài đặt thư viện NewtonSoft.Json vào Client và Server.

    Viết code cho Client và Server như sau:

    using Common;
    using Newtonsoft.Json;
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    namespace Client
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                Console.Title = "Client";
                while (true)
                {
                    Console.Write("Press enter to send ... ");
                    Console.ReadLine();
                    var student = new Student
                    {
                        Id = 1,
                        FirstName = "Nguyen Van",
                        LastName = "A",
                        DateOfBirth = new DateTime(1990, 12, 30)
                    };
                    var client = new TcpClient();
                    client.Connect(IPAddress.Loopback, 1308);
                    var stream = client.GetStream();
                    var writer = new StreamWriter(stream);
                    var serializer = new JsonSerializer();
                    serializer.Serialize(writer, student);
                    writer.Flush();
                    client.Close();
                }
            }
        }
    }
    using Common;
    using Newtonsoft.Json;
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    namespace Server
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                Console.Title = "Server";
                var listener = new TcpListener(IPAddress.Any, 1308);
                listener.Start(10);
                while (true)
                {
                    var client = listener.AcceptTcpClient();
                    var stream = client.GetStream();
                    var reader = new StreamReader(stream);
                    var serializer = new JsonSerializer();
                    var student = serializer.Deserialize(reader, typeof(Student)) as Student;
                    client.Close();
                    Console.WriteLine("Deserialized object:");
                    Console.WriteLine($"Id: {student.Id}\r\nFirst Name: {student.FirstName}\r\nLast Name: {student.LastName}\r\nDate of birth: {student.DateOfBirth.ToShortDateString()}");
                }
            }
        }
    }
    Theo dõi
    Thông báo của
    guest

    0 Thảo luận
    Phản hồi nội tuyến
    Xem tất cả bình luận