1. Гость, мы просим Вас ознакомиться с Правилами Форума и Отказом от ответственности!

[обсуждение] стили написания кода

Тема в разделе '.NET', создана пользователем Du10, 13 фев 2014.

  1. TopicStarter Overlay

    Du10 Программист Old school Пользователи

    Сообщения:
    39
    Лайки:
    45
    Пол:
    Мужской
    Репутация:
    1
    Всем здрасьте.

    Хочу обсудить кто как пишет чтение-сохранения файлов, дабы в процессе дискуссии выявить самые красивые и быстрые варианты.
    Начну с того, как пишу я.
    Для примера возмем файл Dynamicobjects.data (Чуть ли не самый простой файл).

    Структура
    Естественно-понятно, что прежде чем писать какой-то редактор или парсер файла - мы разбираем его структуру.
    У динамика структура (для 010) такова:
    PHP:
    struct object
    {
        
    int ID;
        
    int Length;
        
    char Path[Length];
    };

    int sign;
    int Count;
    object Object[Count]<optimize=false>;
    Код
    Далее я пишу класс, который будет содержать заголовочные переменные, и массивы каких-то блоков (их я объявляю структурой). А что бы код можно было легко и непринужденно переносить в другие проекты (и что бы в этом другом проекте не образовывалась свалка) - сразу переименовываю его в соответсвии с типом файла, с которым работаю
    Пример класса:
    PHP:
    using System;
    using System.Text;
    using System.IO;

    namespace 
    DynObj
    {
        class 
    DynObj
        
    {
            protected static 
    int Sign 1347242308;
            protected static 
    DynObj_CS.Templates.Object[] Objects;
        }
    }
    Тут же в проекте создал папку (Folder), и обозвал её DynObj_CS. Тут будут располагаться все классы для работы с этим файлом.
    В ней создал папку Templates. Тут будут располагаться все структуры для этого файла.
    В папке Templates создал класс, и назвал его Object - в него я запишу под структуру Object.
    Содержимое этого класса:
    PHP:
    namespace DynObj.DynObj_CS.Templates
    {
        
    struct Object
        
    {
            public 
    int ID;
            public 
    string Path;
        }
    }
    Чтение в память
    Далее мы будем читать файл в память. Я делаю это так:
    В класс для чтения передаю массив байтов, которые закидываю в MemoryStream, к которому подключаю BinaryReader. Получается очень даже симпатичный код.
    PHP:
    using System;
    using System.Text;
    using System.IO;

    namespace 
    DynObj.DynObj_CS
    {
        class 
    Load DynObj
        
    {
            public static 
    void Start(byte[] fileArr)
            {
                
    MemStream = new MemoryStream(fileArr);
                
    file = new BinaryReader(MemStream);

                
    int inSign file.ReadInt32();
                if (
    inSign != Sign)
                    throw new 
    Exception("Неверный тип файла.");
          
                
    readObjects();

                
    file.Close();
                
    MemStream.Close();
            }

            static 
    void readObjects()
            {
                
    int count file.ReadInt32();
                
    Objects = new Templates.Object[count];

                for (
    int i 0counti++)
                {
                    
    Objects[i].ID file.ReadInt32();
                    
    Objects[i].Path readString();
                }
            }


            static 
    string readString()
            {
                
    int Length file.ReadInt32();
                
    byte[] strArr file.ReadBytes(Length);
                
    string result Encoding.GetEncoding(936).GetString(strArr0Length);
                
    //Кодировка (Encoding.GetEncoding(936)) захардкоженна а не передана в параметре, тк
                //в PW почти везде используется именно она
                //Но для гибкости можно передавать кодировку и в параметре

                
    return result;
            }

            static 
    MemoryStream MemStream;
            static 
    BinaryReader file;
        }
    }
    В основном классе подключаем чтение по имени файла и по массиву байт:
    PHP:
    using System;
    using System.Text;
    using System.IO;

    namespace 
    DynObj
    {
        class 
    DynObj
        
    {
            public static 
    void Load(string fileName)
            {
                
    byte[] fileArr File.ReadAllBytes(fileName);
                
    DynObj_CS.Load.Start(fileArr);
            }
            public static 
    void Load(byte[] fileArr)
            {
                
    DynObj_CS.Load.Start(fileArr);
            }

            protected static 
    int Sign 1347242308;
            protected static 
    DynObj_CS.Templates.Object[] Objects;
        }
    }
    Выгрузка из памяти
    Теперь напишем класс для сохранения всей той информации, что у нас есть.
    PHP:
    using System;
    using System.Text;
    using System.IO;

    namespace 
    DynObj.DynObj_CS
    {
        class 
    Save DynObj
        
    {
            public static 
    byte[] Start()
            {
                
    MemStream = new MemoryStream(GetFileSize());
                
    file = new BinaryWriter(MemStream);


                
    file.Write(Sign);
                
    writeObjects();


                
    byte[] result MemStream.ToArray();
                
    file.Close();
                
    MemStream.Close();

                return 
    result;
            }

            static 
    void writeObjects()
            {
                
    file.Write(Objects.Length);

                for (
    int i 0Objects.Lengthi++)
                {
                    
    file.Write(Objects[i].ID);
                    
    writeString(Objects[i].Path);
                }
            }



            static 
    int GetFileSize()
            {
                
    //Что бы у нас не было много циклов копирования памяти что бы
                //просто расширить её - мы сразу подсчитаем размер, который нам понадобится
                //Можно, конечно, задать какой-то фиксированный большой размер
                //НО для красивого и правильного примера мы так делать не будем
                
    int Size 8;//Header

                
    for (int i 0Objects.Lengthi++)
                {
                    
    Size += 8;//ID и длинна строки

                    
    byte[] tmpArr Encoding.GetEncoding(936).GetBytes(Objects[i].Path);
                    
    Size += tmpArr.Length;
                }

                return 
    Size;
            }

            static 
    void writeString(string Value)
            {
                
    byte[] strArr Encoding.GetEncoding(936).GetBytes(Value);
                
    file.Write(strArr.Length);
                
    file.Write(strArr);
            }


            static 
    MemoryStream MemStream;
            static 
    BinaryWriter file;
        }
    }
    И подключим в основном классе сохранение:
    PHP:
    using System;
    using System.Text;
    using System.IO;

    namespace 
    DynObj
    {
        class 
    DynObj
        
    {
            public static 
    void Load(string fileName)
            {
                
    byte[] fileArr File.ReadAllBytes(fileName);
                
    DynObj_CS.Load.Start(fileArr);
            }
            public static 
    void Load(byte[] fileArr)
            {
                
    DynObj_CS.Load.Start(fileArr);
            }

            public static 
    void Save(string fileName)
            {
                
    byte[] fileArr DynObj_CS.Save.Start();

                
    //Создаем папку для файла - что бы при сохранении не ругался
                
    Directory.CreateDirectory(Path.GetDirectoryName(fileName));
                
    File.WriteAllBytes(fileNamefileArr);
            }
            public static 
    byte[] Save()
            {
                
    byte[] fileArr DynObj_CS.Save.Start();
                return 
    fileArr;
            }

            protected static 
    int Sign 1347242308;
            protected static 
    DynObj_CS.Templates.Object[] Objects;
        }
    }
    Итог
    Вот и всё :) Теперь мы можем загружать и сохранять этот файл. А в промежутке между этими действиями делать с ним всё что угодно :)

    P.S. Архив с этим проектом.
    http://files.mail.ru/E0C7DA0DA3AC4C689DB7025389CFB35E
    Последнее редактирование: 13 фев 2014
    Mr.Fleks, Hardx, DIABLO и 4 другим нравится это.
  2. nom1nal Moderator Команда форума Модератор Программист Пользователи

    Сообщения:
    356
    Лайки:
    117
    Пол:
    Мужской
    Репутация:
    0
    Привет. Код выглядит читабельным и удобным, как для Windows Forms. Попробуй теперь перейти на WPF и для вывода информации использовать Binding
    Сам как то мучался с уймой кода, а потом Андрей(pdev) посоветовал биндить и показал как. Сразу я не понял зачем вообще упрощать вывод информации, а потом Данил (daqqq) привел более подробный пример.
  3. TopicStarter Overlay

    Du10 Программист Old school Пользователи

    Сообщения:
    39
    Лайки:
    45
    Пол:
    Мужской
    Репутация:
    1
    Теперь ждем топика с примером :)
  4. nom1nal Moderator Команда форума Модератор Программист Пользователи

    Сообщения:
    356
    Лайки:
    117
    Пол:
    Мужской
    Репутация:
    0
    От меня? :^)
    Хорошо, только ближе к вечеру, а то сейчас на практику еду.
  5. gouranga Эксперт Программист Пользователи Open Source Contributor White List

    Сообщения:
    67
    Лайки:
    142
    Пол:
    Мужской
    Репутация:
    0
    Страна:
    Netherlands Netherlands
    Ужасный, неживучий, стиль программирования.
    Объяснил в личку почему.
  6. pdev 14:23 Команда форума Администратор Программист Open Source Contributor

    Сообщения:
    1.409
    Лайки:
    1.407
    Пол:
    Мужской
    Репутация:
    7
    Команда:
    Indy
    Страна:
    Turkey Turkey
    мельком пробежался.
    1) используй using для всех наследников интерфейса IDisposable, код сразу станет проще и надежней.
    2) используй var для улучшения читабельности кода
    nom1nal и NEO нравится это.
  7. gouranga Эксперт Программист Пользователи Open Source Contributor White List

    Сообщения:
    67
    Лайки:
    142
    Пол:
    Мужской
    Репутация:
    0
    Страна:
    Netherlands Netherlands
    Попросили разжевать. Ок, погнали.
    0. аааа, название структуры Object! убейте меня!
    1. Использование структур. Структуры изначально появились в c# для того, чтобы взаимодействовать с winapi и всяким com-подобным хламом. Тому есть миллион причин, цитировать книжки я не буду. Используйте классы.
    2. Повсюду static (иногда еще и protected static, sic! :) ). Безумие. Как потом поддерживать этот код? Static-классы в c# для меня это в первую очередь лучшая альтернатива Singleton'у — главному антипаттерну тысячелетия. :)
    3. pdev верно заметил про using. Еще отмечу, что использовать любые потоки как поле в классе должно автоматически вынуждать реализовывать интерфейс IDisposable. Именно поэтому я, например, использую using, чтобы не громоздить говно в куче — его и без нас там хватает. Идеально (для загрузки/сохранения) передавать инстанс Stream/BinaryReader и потом читать. А после прочтения этот инстанс с помощью using уйдет в небытие — лепота.
    Заметьте, что никто не мешает сохранить путь к файлу, заблокировать его для чтения-записи для других или отслеживать внешние изменения в нем.
    4. Конечно же, двойное чтение. При том, ладно бы чтение одной конкретной записи (еще не страшно, удалится на очередной итерации GC), так нет, чтение ЦЕЛИКОВОГО файла в byte[], а потом разбор этого byte[]. Адский ужс. Я поясню: такой подход даже теоретически увеличивает объем потребляемой памяти в два размера загружаемого файла. Загрузили файл размером N*M в byte[], а потом создали массив struct Object размера N. А что если файл будет хотя бы 100мб? Будет потребление > 200 мб. А если бы была поддержка множественного открытия файлов (ее нет из-за static)? :)
    Я всегда говорю, что память не ресурс, т.к. сейчас у всех уже минимум 8 гб, но зачем городить адский ад, когда можно сделать лучше.
    5. Массивы. Это отлично для каких-то вещей, но для нашего конкретного случая — не годится никуда. Хотя бы List. Это достаточно быстрая коллекция.

    Прикрепляю код, который на мой взгляд более удобен в плане поддержки и разработки. Так же он позволит быстро писать ViewModels для байндинга в WPF.
    Ключевые моменты:
    1) Простая загрузка и сохранение:
    Код:
                DynResourceCollection model = null;
                using (var stream = File.Open("dynamicobjects.data", FileMode.Open))
                using (var reader = new BinaryReader(stream))
                {
                    model = reader.ReadModel<DynResourceCollection>();
                }
                using (var stream = File.Open("dynamicobjects-written.data", FileMode.Create))
                using (var writer = new BinaryWriter(stream))
                {
                    writer.WriteModel(model);
                }
    2. Легкореализуемое подключение IoC — кому надо поймет. :)
    3. Легкореализуемое проксирование к модели из ViewModel.
    4. Полностью отдельный механизм чтения/записи данных, в котором можно реализовать поддержку версий файла или свои форматы данных:
    Код:
        internal class DynResourceCollection : List<DynResource>, IBinaryModel
        {
            private const uint Magic = 'D' | 'I' << 8 | 'M' << 16 | 'P' << 24; // DIMP
    
            #region IBinaryModel Implementation
    
            void IBinaryModel.ReadInternal(BinaryReader reader, int version)
            {
                var inSign = reader.ReadUInt32();
                if (inSign != Magic)
                    throw new Exception("Unhandled type.");
    
                int count = reader.ReadInt32();
                for (int i = 0; i < count; i++)
                {
                    var model = reader.ReadModel<DynResource>();
                    Add(model);
                }
            }
    
            void IBinaryModel.WriteInternal(BinaryWriter writer, int version)
            {
                writer.Write(Magic);
                writer.Write(Count);
                for (int i = 0; i < Count; i++)
                {
                    writer.WriteModel(this[i]);
                }
            }
    
            #endregion // IBinaryModel Implementation
        }
    Код:
        internal class DynResource : IBinaryModel
        {
            private string _path;
            public uint Id { get; set; }
            public string Path
            {
                get { return _path; }
                set { _path = string.IsNullOrWhiteSpace(value) ? string.Empty : value; }
            }
    
    ...
            #region IBinaryModel Implementation
    
            void IBinaryModel.ReadInternal(BinaryReader reader, int version)
            {
                Id = reader.ReadUInt32();
                var length = reader.ReadInt32();
                Path = reader.ReadString(Encoding.GetEncoding("GBK"), length);
            }
    
            void IBinaryModel.WriteInternal(BinaryWriter writer, int version)
            {
                writer.Write(Id);
                var length = Encoding.GetEncoding("GBK").GetByteCount(Path);
                writer.Write(length);
                writer.Write(Path, Encoding.GetEncoding("GBK"), length);
            }
    
            #endregion // IBinaryModel Implementation
        }
    Да, кода стало чуть больше. Но он: быстрее, ест меньше памяти, в нем проще разобраться.

    Вложения:

    • TestLoader.zip
      Размер файла:
      6,3 КБ
      Просмотров:
      36
    nom1nal, this, TheCooler и 6 другим нравится это.
  8. pdev 14:23 Команда форума Администратор Программист Open Source Contributor

    Сообщения:
    1.409
    Лайки:
    1.407
    Пол:
    Мужской
    Репутация:
    7
    Команда:
    Indy
    Страна:
    Turkey Turkey
    Я обычно пишу наследников BinaryReader, BinaryWriter, для удобного, например, чтения/записи строк фиксированной длины, можно еще как-то кастомизировать, если хочется упростить основной код приложения.
    Чтение файла целиком в память, действительно спорный и не универсальный подход, не люблю я его. Во всяком случае читать бесполезный массив байт... какой смысл?
    Массивами, уже не помню, когда последний раз пользовался, без линка не представляю как жить.
    Offtop: На днях ковырялся в Unity-проекте: убила неполная интеграция C# (юнити не видит свойств (get;set), работает только с паблик-полями классов, что несколько непривычно после биндинга WPF. На мой взгляд свойства очень удобны в моделях. Также не поддерживаются namespace, что вообще мозг сломало.

    Du, за тему - респект. Не каждый решиться "светить" свой код. А объективная критика - это уникальный способ прокачать свой навык.
    gouranga респект за детальный разбор полетов и затраченное время.
    Побольше таких тем, от них больше пользы, чем от троллинга по любому поводу.
    Последнее редактирование: 13 фев 2014
    nom1nal и NEO нравится это.
  9. gouranga Эксперт Программист Пользователи Open Source Contributor White List

    Сообщения:
    67
    Лайки:
    142
    Пол:
    Мужской
    Репутация:
    0
    Страна:
    Netherlands Netherlands
    Ну, наследник-то может быть один, либо List либо Reader-Writer, а городить своих клонов с мультифункционалом — нелогично же.
    nom1nal и NEO нравится это.
  10. pdev 14:23 Команда форума Администратор Программист Open Source Contributor

    Сообщения:
    1.409
    Лайки:
    1.407
    Пол:
    Мужской
    Репутация:
    7
    Команда:
    Indy
    Страна:
    Turkey Turkey
    Ну да, реализация c BinaryExtensions поизящнее будет.
    NEO нравится это.
  11. nom1nal Moderator Команда форума Модератор Программист Пользователи

    Сообщения:
    356
    Лайки:
    117
    Пол:
    Мужской
    Репутация:
    0
    После прочитанного задумался стоит ли выдавать исходник в том виде в котором подготовил. Благодаря советам Сергея появились мысли о переписывании редактора на WPF с выводом и хранением информации с помощью биндинга. Сергей, огромное спасибо за все советы и объективную критику.
    Мерфи нравится это.
Черновик сохранён Черновик удалён

Поделиться этой страницей