Morpheus_God
@Morpheus_God

Почему такая большая разница в объеме данных?

Добрый день.
Потребовалось мне в учебном проекте пересылать данные от клиента к серверу и обратно. Я написал простое подобие "менеджера пакетов".
Класс для записи данных в поток
public class DataWriter : BinaryWriter 
    {
        private MemoryStream _ms;
        private BinaryFormatter _bf;

        public DataWriter()
            :base()
        {
            _ms = new MemoryStream();
            _bf = new BinaryFormatter();
            OutStream = _ms;
        }

        /// <summary>
        /// Method for Objects.
        /// </summary>
        /// <param name="o"></param>
        public void WriteObject(object o)
        {
            _bf.Serialize(_ms, o);
        }

        public byte[] GetBytes()
        {
            Close();

            byte[] data = _ms.ToArray();

            return data;
        }
    }

Класс для чтения данных из потока
public class DataReader :BinaryReader
    {
        private BinaryFormatter _bf;

        public DataReader(byte [] data)
            :base(new MemoryStream(data))
        {
            _bf = new BinaryFormatter();
        }

        public T ReadObject<T>()
        {
            return (T)_bf.Deserialize(BaseStream);
        }
    }


Решил я протестировать в консольном приложении. Создал простой класс.
Класс Artist
[Serializable]
    public class Artist
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string SongTitle { get; set; }
    }

Код для проверки работы
static void Main(string[] args)
        {

        
            Console.Title = "Data Into Packets Lib. Tester";

            Artist artist = new Artist();
            artist.FirstName = "Michael";
            artist.LastName = "Jackson";
            artist.SongTitle = "Give In To Me";

            DataWriter dw = new DataWriter();
            dw.WriteObject(artist);

            byte[] data = dw.GetBytes();
            Console.WriteLine($"Values:\nName: {artist.FirstName}.\nLastName: {artist.LastName}.\nSong Title: {artist.SongTitle}");
            Console.WriteLine($"Class object size: {data.Length} bytes");

            dw = new DataWriter();
            dw.Write(artist.FirstName);
            dw.Write(artist.LastName);
            dw.Write(artist.SongTitle);

            data = dw.GetBytes();

            Console.WriteLine($"Simple variable size: {data.Length} bytes");
            Console.ReadKey();
        }
    }


И я был удивлен такой огромной разнице между посылкой объекта и посылкой данных по частям.
Если посылать весь объект то размер выходит 240 байт, при посылке данных частями размер всего 30 байт.
По какой причине так происходит? Я понимаю, что при сериализации происходит полный проход по графу объекта но не такой же ценой. Класс предельно простой. Или же я чего то не так написал?
  • Вопрос задан
  • 134 просмотра
Решения вопроса 2
Radjah
@Radjah
Посмотри пакеты с помощью tcpdump или подобного сниффера, сразу всё на свои места встанет.
Ответ написан
В первом случае в байтовом массиве следующая строка (238 байт):
\0\u0001\0\0\0ÿÿÿÿ\u0001\0\0\0\0\0\0\0\f\u0002\0\0\0<Test1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\u0005\u0001\0\0\0\fTest1.Artist\u0003\0\0\0\u001a<FirstName>k__BackingField\u0019<LastName>k__BackingField\u001a<SongTitle>k__BackingField\u0001\u0001\u0001\u0002\0\0\0\u0006\u0003\0\0\0\aMichael\u0006\u0004\0\0\0\aJackson\u0006\u0005\0\0\0\rGive In To Me\v"

Если поменять автосвойства в классе Artist на полные,
[Serializable]
public class Artist
{
    private string _firstName;
    private string _lastName;
    private string _songTitle;

    public string FirstName { get => _firstName; set => _firstName = value; }
    public string LastName { get => _lastName; set => _lastName = value; }
    public string SongTitle { get => _songTitle; set => _songTitle = value; }
}
размер станет меньше (190 байт) и строка поменяется соответственно:
\0\u0001\0\0\0ÿÿÿÿ\u0001\0\0\0\0\0\0\0\f\u0002\0\0\0<Test1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\u0005\u0001\0\0\0\fTest1.Artist\u0003\0\0\0\n_firstName\t_lastName\n_songTitle\u0001\u0001\u0001\u0002\0\0\0\u0006\u0003\0\0\0\aMichael\u0006\u0004\0\0\0\aJackson\u0006\u0005\0\0\0\rGive In To Me\v

Во втором же случае (раздельная запись каждого свойства) эта строка выглядит так (30 байт):
\aMichael\aJackson\rGive In To Me

В этом мне помог глупый метод:
private static string BytesToString(byte[] bytes)
{
	char[] chars =  Encoding.UTF7.GetString(bytes).ToCharArray();
	StringBuilder result = new StringBuilder();
	foreach (char c in chars) { result.Append(c); }
	return result.ToString();
}

Какие мы можем сделать выводы из всего этого?

Первый: при сериализации объекта помимо значений свойств записывается еще и структура самого объекта, названия полей, имя и версия сборки и все в таком духе (причем, как можно видеть, в случае с автосвойствами весь этот оверхед больше). К тому же автосвойства не подходят для сериализации и десериализации, в разных сборках их фактические имена будут отличаться. Во втором случае вы записываете просто значения трех строк без структуры.

Второй: написание всего этого кода заняло у меня несколько минут, и не понятно, что же мешало вместо поста в интернете написать небольшой отладочный метод и посмотреть, что же, собственно, происходит. Неужели было не любопытно разобраться самостоятельно?
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы