C# - быстро склонировать несложный объект
Есть один или пачка POCO, пусть даже с какой-то иерархией наследования. Какой способ быстро склонировать эту пачку в наше время?
Раньше народ клепал свои методы на каждый такой класс. Но это надо не забыть исправить такой метод, если исправляется какое-то свойство в клонируемом типе. Да и больше подходит для кастомного копирования с изменениями (т.е. это уже не копирование, а сериализация будет), а не для простого "один к одному".
Потом варианты через сериализаторы - если не сотни мегабайт объектов так клонировать, то пойдёт. Особенно, если не нужно расставлять атрибуты сериализации - т.е. подойдут всякие JSON-сериализаторы.
Ещё с деревьями выражений находил примеры. Но они какие-то замудрёные. Хотя и говорят, что это быстрее всего.
Есть ещё разные фреймворки, но по-моему они внутри делают что-то из вышеперечисленного.
Тут чел собрал разные методы, при этом топит за ручное клонирование. Но то 2012 год. Да и уже говорил, что это скорее для сложного клонирования не "один к одному" и разных не-POCO вещей.
С сериализаторами просто - Serialize, Deserialize, и вот у тебя полная копия POCO. Собственно, сейчас с ними клонирую.
В шарпе есть IClonable.
Который делает примерно ничего, кроме объявления контракта, что тут что-то клонируется - всё должен делать ты сам, причём ручками. Мне-то надо, чтобы работать поменьше, а результата побольше.
Чтобы не забыть клонировать новые поля можно написать юнит-тест и рефлекшенами считать количество (и названия) полей. Этот прием также можно использовать для тестирования Equal.
Что-то невероятно замудрённое. ))
Короче, продолжаю юзать это - не нужно никаких атрибутов дописывать, интерфейсы реализовывать, и вообще ничего не нужно - тупо на любой POCO класс применяется из коробки:
var temp = JsonConvert.SerializeObject(original);
var copy = JsonConvert.DeserializeObject(temp);
Короче, продолжаю юзать это - не нужно никаких атрибутов дописывать, интерфейсы реализовывать, и вообще ничего не нужно - тупо на любой POCO класс применяется из коробки:
Как оно с private свойствами работает?
Падает ли, если в дереве объектов окажется циклическая зависимость?
Универсального метода не будет видимо. Выбираем по ситуации
https://www.wwt.com/article/how-to-clone-objects-in-dotnet...
https://learn.microsoft.com/en-us/dotnet/api/system.object...
https://www.codeproject.com/Articles/23832/Implementing-De...
Как оно с private свойствами работает?
Падает ли, если в дереве объектов окажется циклическая зависимость?
Для приватных надо атрибуты расставлять. Или настройки для всего типа делать (т.е. тоже атрибут).
Цикл можно заигнорить.
Другой объект в составе сериализуемого можно сериализовать со всеми его данными, а можно лишь ссылку на него.
вот это ты смачно в лужу пукнул.
IClonable и был придуман для того, что хочет Алекс.
Никто кроме самого объекта не может знать как надо клонироваться (и надо ли вообще). Так что правильный путь - реализация IClonable. Все остальное - костыли, которые посыпятся в самый неподходящий момент.
короче нет серебряной пули (((
Плюс у тебя во второй ссылке реестр идёт про библиотеку Newtonsoft
Конечно пулит нет. Я же упростил и говорил про POCO.
Эту либу я и раньше юзал. МСовская была не очень раньше. Они её дорабатывают, но всё равно не дотягивает до json.net. Но суть та же - клонирование несложных объектов через сериализацию до сих пор самый простой способ клонирования, чем вводить свои копировщики, интерфейсы и прочее. Если будете свои копировщики вводить, то столкнётесь с теми же проблемами, что и при сериализации - как ссылки обрабатывать, как циклические зависимости и прочее. И придёте к тем же решениям - вводить нотацию для ссылок или копировать как есть с дублированием данных, опция для игнора циклов, и т.п. Только сами вы это будете неделями и месяцами разрабатывать со всеми сопустствующими ошибками. А сериализаторы уже готовы и отлажены. Проще добить свои классы до сериализации (расставить атрибуты) и воспользоваться ей, чем вводить свои копировщики.
IClonable - старый дотнетовский интерфейс, введённый ещё до появления других удобных инструментов клонирования. Он никак не помогает клонировать, т.к. не содержит никаких вспомогательных инструментов, а просто декларация, широко используемая в самом Дотнете. Написать интерфейс клонирования с одним едиственным методом без реализации - что может быть проще. А вот реализовать... Атавизм, как и класс HashTable и многие другие, оставленные для совместимости.
вот это ты смачно в лужу пукнул.IClonable и был придуман для того, что хочет Алекс.
Никто кроме самого объекта не может знать как надо клонироваться (и надо ли вообще). Так что правильный путь - реализация IClonable. Все остальное - костыли, которые посыпятся в самый неподходящий момент.
С интерфейсом проблема в том, что он декларирует наличие реализации, но никак не отслеживает её актуальность при изменении типа. Тип изменился, и разработчик должен посмотреть, какие другие части этого типа тоже должны измениться. Со сложными типами это море работы, и всё равно можно что-то упустить. Тогда как атрибуты находятся там же, где и изменяющиеся вещи в типе - прямо над ними. Так что забыть изменить сериализацию сложнее - это надо специально атрибуты игнорировать.
Предлагают обложиться тестами, ага. Только как в тестах отслеживать, что там теперь должны быть новые члены? Создавать тестовые объекты через рефлексию, проходя по всем текущим свойствам и сравнивая с объектом после сериализации? Вы тогда тесты будете разрабатывать дольше, чем сами типы, а их сложность может потребовать тесты над тестами. А по заветам отцов-основателей, тесты должны быть максимально простыми.
Посмотрите на количество имеющих этот интерфейс внутренних классов Дотнета (список "Derived") -
https://learn.microsoft.com/en-us/dotnet/api/system.iclone...
Все они юзают его, а не какой-то конструктор-копировщик. Последний - действительно пришёл из плюсов.
Этот интерфейс можно использовать, если у вас какая-то сложная схема копирования объектов, требующая логики реализации, которую нельзя просто атрибутами обозначить. Ну и если хотите синхронизироваться с другими системами, которые на этот интерфейс рассчитывают. А мне для простых объектов неохота ничего писать, а охота как можно быстрее. А уж если эти объекты и так должны сериализироваться - т.е. я их для этого уже подготовил - то тем более грех не воспользоваться уже готовым инструментом.
Я точно не знаю, как эта ситуация обрабатывается. Ссылочные данные могут быть сериализованы ссылками в той либе. Но ссылке в джейсоне не стандартизированы, поэтому каждая либа их делает по-своему.
А циклы - можно не сериализовать. Но непонятно, что это значит - вообще поле пропускается, или на глубину 1 всё же сериализуется. Тестировать надо. По идее, если ссылочные типы просто заменять ссылками, то циклов возникать не должно - мы просто пишем для ссылочного поля его ссылочный айди (в нотации сериализованного джейсона - см. выше мой линк на ту либу), и дальше вглубь это поле не сериализуем. Всё, от цикла избавились.
Но опять же, логика присвоения эти ссылочных айди - чтобы на одну ссылку разные айди не присвоил. Есть ли там сравнение адресов ссылок, или просто каждому следующему ссылочному полю идёт присвоение инкрементированного айди.
Я хочу сделать у своего проекта in-memory базу данных. Просто таблицы без связей, которые при старте приложения загружаются из текстовых файлов. Таблицы эмулируются банальными словарями, где айдишники это ключи, а значения - объекты. И потом я из этой "базы данных" читаю строки. Когда с обычной базой данных работаешь, ты создаёшь копию такого объекта (строки) у себя в приложении. А в моём варианте, чтобы не копировать ссылку из моих словарей на один и тот же объект, нужна операция клонирования. И т.к. связей между "таблицами" у меня нет, то и циклов у меня нет. В обычных БД, в ORM, вопрос с цикличностью решается ленивой загрузкой, насколько я знаю. Ну и программист сам определяет, насколько глубоко он там связи между таблицами запросит.
Чуваки из игрового мира не привыкли заморачиваться базами данных на клиенте. И в чём-то они правы - возиться ещё с ними. А в Дотнете такой удобный механизм LINQ to objects, да ещё типизированный - очень похожий на LINQ to entities, который используется для БД. И раз уж всё равно объекты сериализовать надо (сохранения, загрузки состояний), то убью двух зайцев одним ударом.
А в моём варианте, чтобы не копировать ссылку из моих словарей на один и тот же объект, нужна операция клонирования
всё равно не доходит. Если это всё реадонди, то какая разница?
Я хочу сделать у своего проекта in-memory базу данных.
типа NoSQL? А зачем то самому делать? Пару строк и все работает. https://www.infoworld.com/article/3672154/how-to-use-ef-co...
Ну или типа этого https://www.litedb.org/
типа NoSQL? А зачем то самому делать? Пару строк и все работает. https://www.infoworld.com/article/3672154/how-to-use-ef-co...
Ну или типа этого https://www.litedb.org/
Я не знаю, что такое nosql, но у меня есть коллеции объектов в памяти, я их сериализую в текстовый человекочитаемый файлик, потом могу подправить его хоть в Блокноте, потом сохранить и десериализовать снова в своё приложение. Если эта штука по последней ссылке так может (особенно человекочитаемый файлик для данных таблицы), то посмотрю её. Но они предлагают какую-то свою Студию юзать для редактирования файлов. Похоже, что файлы таблиц там не человекочитаемые. И редактировать в разных текстовых редакторах я их не смогу.
Я не знаю, что такое nosql
не страшно
https://skillbox.ru/media/code/nosql-chto-eto-za-bazy-dann...
есть коллекции объектов в памяти, я их сериализую в текстовый человекочитаемый файлик
Ломит редакторы делать? Это разве удобно для пользователей?
Но тоже не вижу зачем клонирование...
Делаем словарик <ObjectType, Object>
Делаем цикл по словарю сохраняем каждый в json файл, где ключ имя файла. Для чтения наоборот: цикл по файлам, создаём объекты.
Если не огромная коллекция то можно и всё сразу выгрузить в один файл.
litedb - не для этого
Я не знаю, что такое nosqlне страшно
https://skillbox.ru/media/code/nosql-chto-eto-za-bazy-dann...
Да не, не так. - Я не хочу знать, что такое nosql. )))
Делаем цикл по словарю сохраняем каждый в json файл, где ключ имя файла. Для чтения наоборот: цикл по файлам, создаём объекты.
Если не огромная коллекция то можно и всё сразу выгрузить в один файл.
Не, мне лучше, чтобы на одну таблицу один файл. Мне проще даже не джейсон, а csv. Типа такого
id,name,race,age
0000,Alex,human,50
0001,Malex,thuman,100500
Но для csv надо будет писать свой сериализатор-десериализатор, а для джейсона уже всё есть.
Редактор пока не нужен. Ну или хватит текстового. Ну или тупо список объектов в датагрид загрузить с возможностью редактирования их свойств через редактирование ячеек датагрида.
Он имеет ввиду, что при клонировании ссылки на объект восстанавливаются как ссылки, а не как объекты со своими полями. Но при сериализации и десериализации ссылки теряются, т.к. уже может не существовать объект, на который ссылаются. Либа по джейсону, что я предлаг, может сохранять ссылки.
Сама идея сериализации - мы вытаскиваем какой-то объект или граф объектов из контекста и разрываем все свази этого графа объектов с контекстом. При десериализации мы восстанавливаем данные графа, но не его связи с контекстом, т.к. обычно уже сам контект либо поменялся, либо уже не существует. Поэтому пре сериализации нет смысла сохранять все ссылки, а только те, что связывают объекты в сериализуемом графе.
Мне надо создать копию объекта без потери контекста, т.к. я генерю её тут же - контекст не успевают измениться. Но я создаю копии лишь данных, а не ссылок. Новую копию я ввожу в контекст, создавая для неё свои ссылки, а не копируя ссылки оригинала. В частности, я генерю для копии новый айдишник. И теперь это копия лишь в смысле данных, но ссылаться на неё нужно уже по своему уникальному айдишнику.
Таблицы эмулируются
-----
Нахрена?
Таблица, со всей функциональностью, уже есть.
Делаешь ее строго типизированной и забываешь об 95% проблем.
Мне надо, чтобы сохранённый вариант был человекочитаемый и достаточно легко редактируемый в текстовом редакторе типа Блокнота. Джейсон не нравится тем, что он некомпактный - даже несколько десятков записей будут требовать много скроллинга для просмотра. А вот формат csv очень даже компактный - можно эти несколько десятков уместить на один экран.
Тогда согласен. Просто я думаю о своём, а у меня данные компактные. Единственное что, в csv вроде только один символ-разделитель можно сделать. И даже если это будет таб, то столбцы всё равно неровными могут быть. Два таба в качестве разделителя вроде нельзя. Хотя, там стандарта на разделитель нет - как считывающий код сделаешь, так и будет... Вобщем, csv - компромиссный вариант. Но он мне в любом случае не нужен, т.к. генерить его надо ручками, а не автоматом натравил либу на класс и она всё сериализировала почти как надо, без расстановки атрибутов и громождения конфигураторов.
Мне надо, чтобы сохранённый вариант был человекочитаемый и достаточно легко редактируемый в текстовом редакторе типа Блокнота.
А вот формат csv очень даже компактный - можно эти несколько десятков уместить на один экран.
Уместить-то может быть и можно (хотя зависит от длины хранимых данных), а вот найти нужное поле или не дай бог редактировать - это трэш.
Датасет же ин-мемори коллеция данных, а не на диске. Т.е. к нему нужно иметь внешнее хранилище так и так. И ещё нужна отдельная прога для поддержки датасетов и редактирования через них данных - чтобы хотя бы грид с возможностью редактирования показала.
И датасет это не один файл, а кучка (минимум 4, вроде, на которые разваливается .xsd), описывающих один датасет.
У датасета и дататейбл из всей нужной функциональности - поддержка IList, IEnumerable и сериализации в XML. Сериализовать POCO в XML или джейсон можно и так из коробки. И загнать свои данные во что-то IEnumerable или IList я тоже легко могу - банальный словарь. И затем запрашивать их с полной функциональностью sql-подобных запросов LINQ to objects. Тогда зачем мне вся эта инфраструктура датасетов, если поддерживать её сложнее, и редактировать быстро извне в простой сторонней программе типа Блокнота нельзя?
Датасет же ин-мемори коллеция данных, а не на диске.
Именно так.
Т.е. к нему нужно иметь внешнее хранилище так и так.
Храни как хочешь. Хоть в XML :)
И ещё нужна отдельная прога для поддержки датасетов и редактирования через них данных - чтобы хотя бы грид с возможностью редактирования показала.
Зачем? :) Если в ТЗ такого нет, то не нужна. А если в ТЗ есть, то ты и со своими классами должен эту прожку написать.
И датасет это не один файл, а кучка (минимум 4, вроде, на которые разваливается .xsd), описывающих один датасет.
WriteXML создает одни XML файл. А ReadXML (внезапно) этот XML читает и воссоздает DataSet.
Тогда зачем мне вся эта инфраструктура датасетов
Если тебе нужна лайт BD, то DataSet оптимальное решение. А еще DataSet можно подружить с твоим любимым EF и вообще кайф :D
редактировать быстро извне в простой сторонней программе типа Блокнота нельзя?
Ну конечно можно. XML вполне себе поддается редактированию блокнотом :) А еще можно там искать данные при помощи XPath
и менять только то, что надо :D :D :D
из всей нужной функциональности
-----
Из всей известной тебе функциональности.
Там вполне работает LINQ, кроме этого - фильтрация по SQL.
Тогда зачем мне вся эта инфраструктура датасетов, если поддерживать её сложнее, и редактировать быстро извне в простой сторонней программе типа Блокнота нельзя?
-----
Это ограничения твоих знаний...
Если тебе нужна лайт BD, то DataSet оптимальное решение. А еще DataSet можно подружить с твоим любимым EF и вообще кайф :D
Мне нужна ещё более лайт, чем датасеты - без связей между таблицами. Чем пачка List<T> или Dictionaty<T,T> не лайт ин-мемори ДБ без связей? Просто она не relational. И EF не нужен, т.к. всё и так типизировано. ))
Вопрос лишь в том, как хранить на диске. С датасетами - ок, можно писать-читать XML. Но и List<T> можно сериализовать в джейсон без проблем, если там POCO без связей.
Теперь другой вопрос - мне надо ещё и сериализовать граф объектов (состояние программы). Тут датасеты не нужны, а сераилазация снова в деле. Итого, для разных задач могу использовать лишь одну либу (например, для сериализации в джейсон), или разные либы (для сериализации в джейсон и для простейшей БД).
Посмотрел в Unity 3d - вроде, System.Data.dll подключается. Ок, можно оставить на заметку.
Посмотрел best practices в других небольших игровых проектах - не юзают люди БД. Хранят всё в джейсоне или YAML, редко в XML. Потом всё грузят в память и читают из памяти как из хранилища (базы данных). Там сама идея, что зависимости тебе нужны лишь при работающей программе - ты грузишь объекты из БД и создаёшь связи между ними. А в самой БД связи не нужны. А если хочешь состояние со связями сохранить, то сериализуешь, а не в БД сохраняешь. Чтобы в БД сохранить состояние, это надо, чтобы все объекты состояния присутствовали в БД в виде таблиц со связями. А это зачастую не нужно.
Первое данное я создам в коде и сериализирую на диск в виде джейсона скорее всего. Потом вручную буду редактировать джейсон - добавлять новые данные. Там же просто массив будет, так элементы массива копипастой добавляешь и редактируешь.
В программе нет редактора данных. Отображение есть, а редактора нет. В теории можно сделать, но там стандартного грида из коробки нет, а эмулировать списком и по отдельности поля редактировать - муторно. В принципе, всё можно сделать, но мне проще не заморачиваться этим, а редактировать извне в каком-нибудь блокноте.
Потом вручную буду редактировать джейсон - добавлять новые данные
А в чём разница добавлять или редактировать?
А что то типа этого чем не нравится?
Это для редактора, не для рантайма. Редактор в Юнити - это когда делаешь игру, а рантайм - когда её запускаешь.
Да в принципе много можно чего найти. Бесплатного и платного. При желании на Юнити можно сделать какой угодно датагрид с привязками и прочим. Ну будет сколько-то стоить - можно заплатить. Просто мне это сейчас не надо. Я хочу как можно быстрее и проще избавиться от рутины и делать то, что интересно. Проще добавить пару объектов в коде, сохранить, и потом раз в неделю добавлять ещё в качестве теста по паре объектов в текстовом файле, чем писать самому какой-то сложный контрол для редактирования в игре, или искать подобный уже готовый и его осваивать.
Если захочу не вручную, то можно что-то типа такого использовать.
Я щас пытаюсь организовать в своих классах как я хотел. Как я раньше говорил - скормил объект сериализатору и всё - нет. Нужно придерживаться определённых правил при проектировании классов. Например, если конструкторов много разных, и есть по-умолчанию (без параметров), то нужно повесить атрибут сериализации на конструктор со всеми необходимыми параметрами, а если такого конструктора нет - создать его. Иначе при десериализации получишь объект с дефолтными полями (будет использован конструктор без параметров). Ну, это завязано на конкретный сериализатор. Можно через всякие дата контракты, но это муторнее.
Ещё раз, у меня такая идея. Есть импровизированная база данных в виде файликов (текстовые - джейсоны, csv или ещё что-то - неважно). Один файлик - одна таблица. Связей в базе данных между таблицами нет - это просто хранилище отдельных классов объектов. Скажем так, дефолтных параметров этих объектов. Там уникальные идентификаторы - текстовые поля (названия этих классов объектов). Например, две таблицы с идентификаторами "name"
таблица Characters:
className Warrior, attack 10, defence 5
className Archer, attack 5, defence 3
...
таблица Items:
className Knife, damage 2, price 400
className Sword, damage 5, price 1200
...
В базе данных может быть только один объект с уникальным именем класса, но в программе их может быть несколько - можно создать несколько Warrior, которые поначалу могут быть даже с одинаковыми параметрами, но потом могут начать различаться. Поэтому это уникальные Warriors, и им нужны свои идентификаторы - я выбрал инты, и затолкал их всех в словари , где T - тип объекта из файла (Warrior, Item, etc.). А в будущем нужно ещё добавить и понятные текстовые имена для персонажей однинаковых классов, например.
При запуске программы все таблицы из базы данных загружаются в виде обычных списков: List characterTable, List itemTable. Т.е. в них хранятся образцы объектов с дефолтными параметрами. И вот, чтобы создать уникальный объект из списка с образцами и добавить его в словарь, мне надо его склонировать. Т.е. я делаю так
var newCharacter = characterTable.FirstOrDefault(c => c.Name == name)
затем клонирую newCharacter через сериализацию-десериализацию, а потом приписываю newCharacter уникальный интовый идентификатор, и сохраняю его в словаре .
А потом эти уникальные объекты в словарях нужно ещё сохранять через сериализацию и потом восстанавливать их.
Т.е. сериализация служит для двух вещей, выделенных жирным: клонирование объектов и сохранение состояния программы. И классы, которые можно сериализовать, нужно специально проектировать. В принципе, ничего особенного, но порасставлять атрибуты придётся.
Вобщем, я эмулирую СУБД и ORM, только очень просто. Использовать настоящую СУБД нельзя, т.к. Юнити их не поддерживает, а заниматься установкой муторно. Все в небольших проектах делают это тупо в текстовых файлах, чтобы не заморачиваться, тем более что списки дефолтных объектов обычно небольшие, и применение для этого СУБД не несёт никакого преимущества. А ещё желательно иметь возможность редактировать эти списки в самом простом подручном редакторе (типа Блокнота). Вы же для СУБД тоже имеете специальную программу для редактирования - та же SQL Server Management Studio это нехилая такая программулина. А без неё будете в командной строке таблицы редактировать и выводить.
говнокод какой-то.
почему не сделать чот типа Repository или Factory, которая при инициализации запоминает, что Warrior‘а нужно создавать со свойствами attack 10, defence 5?
В Repository делаешь функцию T GetInstance<T>() where T : Character и все чистенько без дрочки с серей и десерей.
Я так и делаю - у меня есть класс репы, который всё это и делает. А роль фабрики выполняет клонирующий метод.
Свойства attack 10, defence 5 надо где-то хранить, а ещё редактировать. Не в коде же? Поэтому вот во внешних файликах, эмулирующих таблицы.
Ещё раз, была бы легко доступна нормальная СУБД, я бы её использовал. Но в Юнити всё сделано, чтобы использовали их продукты и привязывались к их движку, а всё постороннее интегрируется сложно. Так все делают, не только они.
В принципе, подошли бы наверное мини СУБД из ссылок АлексНека, если весь движок это одна ДЛЛка. Но они там непонятно в каких файлах хранят данные. А мне нужно простые для редактирования.
Ага, в проге есть объекты, которым нужно сохранять состояние и создавать их на основе начальных данных.
И процесс создания объекта назван "клонированием". Ну да ладно, можно и так посмотреть, хотя это должны быть два различных объекта.
И в сериализации когда то будет проблема обновления объекта.
ну и еще если у тебя по условию задачи в файлике один хер каждый тип только один раз записан, то почему его свойства не прописывать в стандартном конструкторе класса?
Ты сначала объяснял по-другому, якобы тебе нужно инициировать много перосонажей одного типа.
нифига ты не так делаешь.
у тебя список эталонных объектов и каждый раз ты берешь один и клонируешь его
А какая разница, как ваш GetInstance создаёт этот инстанс? Хочу и клонирую - я же расписал, как, зачем и почему так делаю. Весь "говнокод" за этим методом спрятан. Будет СУБД, поменяю на СУБД, а метод останется тем же. Всё по фен-шую, но пока без СУБД.
И в сериализации когда то будет проблема обновления объекта.
Конечно будет. И её никак не решить по-нормульному, чтобы все острые углы обходить. Обычно обвешивают атрибутами и дата контрактами, которые отсутствующие поля игнорируют,или оставляют, но помечают как устаревшие и неиспользуемые, новые добавленные поля дефолтят, или что-то подобное. Сохранённые состояния нужно тоже обновлять. Или забыть про них и продолжить с новыми. Так поступают везде - настройки программы в БД, сохранения из игр, несовместимые с новыми версиями. Области разные, а подход один.
ну и еще если у тебя по условию задачи в файлике один хер каждый тип только один раз записан, то почему его свойства не прописывать в стандартном конструкторе класса?
Ты сначала объяснял по-другому, якобы тебе нужно инициировать много перосонажей одного типа.
Так и осталось - нужно инициировать много персонажей одного типа. Для этого клонирую их из списка, созданного из файлика.
В конструкторе хардкодить - это в исходниках значит. А мне нужны внешние конфигурационные файлы, чтобы легко можно было в блокнотике подправить.
Во! Назову это конфигурационными файлами. А то что-то я сущности придумываю новые, а по сути это конфиги и есть.
В Repository делаешь функцию T GetInstance() where T : Character и все чистенько без дрочки с серей и десерей.
Я может не понял, что вы сказали, но нужно не ссылку на объект из репы получить, а копию объекта - т.е. скопировать все поля. Т.е. склонировать. Это надо либо вручную писать для всех полей операции копирования, либо через сериализацию-десериализацию автоматически (ну, расставив пару-тройку атрибутов). При этом при изменении состава полей сериализацию проще поддерживать - по сути надо лишь отредактировать конструктор с параметрами. Этот конструктор со всеми параметрами - аналог копировщика из С++.
А сериализация для клонирования используется ещё и потому, что я её же использую и для сохранения состояния объектов. Если бы без этого, то можно было бы просто написать свой конструктор-копировщик. А так - немного меньше работы.
Кроме персонажей, которые существуют в единственном экземпляре, есть ещё враги, которых может быть много с одними и теми же параметрами. А кроме того, надо бы подумать, как и персонажей тоже клонировать. Скажем, есть базовый типа "воин", и игрок может создать себе отряд из них, но разбросать им дополнительные характеристики к базовым. Это типичная возможность таких игр с незапамятных времён. Ну и ещё неплохо бы предусмотреть возможность клонировать любого текущего персонажа или врага - на какую-нибудь будущую игровую механику с клонированием. А если заложить возможность сериализации для сохранения состояния, то клонирование получается дополнительно почти автоматически. Нужно только добавить генерацию уникального айдишника. Мой метод клонирования так и делает.
существуют в единственном экземпляре, есть ещё враги, которых может быть много с одними и теми же параметрами
Имеется ввиду класс врагов. Скажем, есть "воин", есть "крутой воин", и у них разные базовые характеристики. Но я могу пойти ещё дальше, и добавить при создании врага из базовых характеристик небольшой разброс, чтобы клоны немного отличались. Это такая особенная игровая механика. Не так уж много где это реализовано - насколько я знаю, японцы вообще этим не заморачиваются. А у меня будет особенность. Ну и по-любому это всё надо добавлять в коллекцию уже созданных и существующих в игровом мире врагов с уникальными идентификаторами. Вот поэтому механика клонирования и нужна.
Ещё раз, если сохранение состояния через сериализацию даёт возможность сохранять весь граф объектов с уникальными параметрами, то не грех воспользоваться этим для клонирования, чтобы не писать отдельные клонирующие методы, которые будут делать то же, что и сериализатор. Мой клонирующий метод будет просто сериализовать-десериализовать любой уже существующий объект с помощью любого готового фреймворка (в данный момент это Json.NET), и добавлять уникальный айдишник.
То, что я в начале говорил, это была общая идея. По мере реализации она обрастает подробностями. Но общее описание не изменилось - клонирую из списка базовых конфигураций. Отличие от типичной БД в том, что обычно в базах данных хранят уникальные объекты, а не их классы. У меня это будет храниться в сериализованном виде в файле, т.к. сохранения состояния должны быть легко портабельными (скинул файлик на флешку, или передал по сети).
Вы когда-нибудь делали программу с сохранением состояния? Не просто конфиги или ненадолго один объект в сессии сохранить, а скажем все открытые окна, вкладки и значения в них? Или скажем открыл окно, вбил значения, закрыл, а вбитые значения не стёрлись, а запомнились, и при открытии такого окна снова, то же самое было бы на тех же местах? И чтобы можно было в любой момент засейвить состояние приложения и загрузить потом тоже в любой момент? Это сложнее, чем просто сохранять некоторые настройки в конфигах. Это надо изначально классы объектов и окон писать с возможностью сохранения состояния. Вот Студию вы закрыли, открыли, а все вкладки, инструменты и даже позиция курсора запомнились и открылись так, как были.
Хотя, пример с сохранением состояний окон наверное не очень. Лучше подходит какой-нибудь редактор - графики или там звука. Свои настройки такая программа хранит в конфигах, но весь граф объектов, представляющий картинку (включая слои, фильтры, их настройки и прочее) или звуковую композицию, должен поддерживать сохранение и загрузку состояния - т.е. быть сериализуемым.
Ну а если спроектировано всё с сохранением состояния, то клонирование через сериализацию получается почти из коробки.
Тогда дефолтная позиция. Также, как и при десериализации, когда кто-то поправил руками джейсон там или исходный класс, и теперь между ними несоответствие.
Большие многомиллиардные корпорации забивают на все эти мелкие проблемы маленьких людей, и если что не соответствует, то говорят просто обновиться и забить на старьё - старые сейвы в новых обновлениях могут не работать, старые конфиги в новых версиях ПО также, и т.д. А вы тут, мелкие сошки, с доходом на всю контору как пара зарплат менеджеров ФААНГов, стараетесь все-все нюансы учесть, вплоть до "а если метеорит упадёт, тогда что? в вашем ПО есть защита от этого?". Проще надо быть. )))


