Разогрею: всё таки непонятно, как вы с этим языком работаете?
Вот, собрал модель данных:
class ScienceModule
{
public int class_id; // ClassCategoriespublic string name;
public Dictionary<string, string> descriptions;
public int production_cost;
public List<int> resource_cost;
public string tech_pic;
// class_ids in commentpublic int power;
public int fuel_consume;
public int mass;
public int strength;
public int scan_power;
public int scan_penetr_power;
public int defence_strength;
public int terraform_power;
public int improve_percent;
public int damage;
public int self_damage;
public int range;
public int reload_time;
public int precision;
public int affect_radius;
public int warheads;
..
}
..
class HullSlot
{
public IntPoint pos;
public int stack_num;
public int max_mass;
public int slot_type;
public int direction;
..
};
class Hull
{
public Dictionary<string, string> descriptions;
public List<int> resource_cost;
public string tech_pic;
...
};
...
... много ещё классов и полей
class Model
{
public ResourceDefinitions resources;
public List<ScienceNode> science_tree;
public List<ScienceModule> planet_blocks;
public List<ScienceModule> ship_blocks;
public List<Hull> hulls;
public List<ShipDesign> ship_designs_startup;
public Dictionary<string, int> fleet_amount_startup;
public List<UniqueID> inventions_startup;
// group of set/update methodsvoid UpdateScienceModule(ScienceModule newModule);
...
}
Пишу редактор модели. Биндю всё это к UI (WPF).
Собственно всё что хочется, выдать клиенту это всё в readonly формате. А если UI меняет что-то, связывать это через явные вызовы сеттеров в модели.
Не трогая пока сеттеры, как выдать этот класс UI-ю read only?
Здравствуйте, johny5, Вы писали:
J>Разогрею: всё таки непонятно, как вы с этим языком работаете?
Элементарно:
1. Изучить матчасть
2. Если что-то непонятно — см п.1.
Как спросили, так и ответил
J>Не трогая пока сеттеры, как выдать этот класс UI-ю read only?
Так в readonly или "через сеттеры"?
если коротко:
1. public-поля зло. Используйте свойства.
2. Двусторонний биндинг требует реализацию INotifyPropertyChanged + ObservableCollection/IBindingList для коллекций.
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, johny5, Вы писали:
S>если коротко: S>1. public-поля зло. Используйте свойства.
Пока не вижу выгоды от проперти вообще и в данном случае в частности. Хотя видел народ предлагает делать { get; private set; }, только это не удобно в моём случае. Я бы ещё покопал в сторону public/internal, хотя вроде бы чтобы internal нормально работал, мне нужно будет собирать отдельную assembly?
S>3. Что именно вы имеете в виду под "ридонли"? Чтобы не изменялись поля, поля у полей, поля у объектов в списках и т.д.?
Да, обыкновенная Deep constness.
S>Прям из коробки такого нет и не будет, по очевидным причинам.
Что за очевидные причины?
S>Не из коробки: построение больших immutable tree — это та ещё магия. Для затравки:
Почитаю ссылочки завтра. Как раз затравливаю, чтобы народ поучил меня уму-разуму, мож пойму чего. Уже 3й раз штурмую эту "крепость" и, пока на уровне скриптиков или там коннекта с DB, C# — прекрасный язык. Начинаешь архитектурить, сразу залазишь по пояс.
S>4. Вопрос очень похож на XY-problem. Давайте начнём с "Для чего тут readonly"?
Ну убедили, давайте поиграем и в эту игру, в параллель.
Я пишу UI редактор этой модели: в частности, добавить "игровую науку", убавить, изменить её значения. Я решил (вроде как по MVVM?) что будет у меня View class, который будет уметь отображать эту модель в редакторе. При переключении всяких табов или выбора подменю, UI должен сам уметь вытягивать нужные данные из модели и показывать на экране. При этом я хотел гарантировать, что UI (не доверяю WPF) случайно не смог изменить саму модель. С какимнть int переменной оно вполне сможет сбиндиться двусторонне. Ну или чтобы я сам впопыхах не впилил прямые изменения модели прямо в код.
Все изменения модели будут проходить через другой (ViewModel?) класс, оно будет подписываться на всякие нужные кнопки само и менять модель. Дополнительно ViewModel будет следить у меня за Undo/Redo, Save/Load и прочие.
J>Пока не вижу выгоды от проперти вообще и в данном случае в частности.
Если коротко — с полями вы в принципе не имеете никакого контроля за возможностью изменения объектов. Классика от Joe Duffy:
struct S
{
public readonly int X;
public S(int x) { X = x; }
public void MultiplyInto(int c, out S target)
{
System.Console.WriteLine(this.X);
target = new S(X * c);
System.Console.WriteLine(this.X); // same? it is, after all, readonly.
}
}
static void Main(string[] args)
{
S s = new S(42);
s.MultiplyInto(10, out s);
Console.ReadKey();
}
S>>Прям из коробки такого нет и не будет, по очевидным причинам. J>Что за очевидные причины?
Для гарантированно неизменяемого графа вам придётся создать свою иерархию типов, в которых нет изменяемых методов/сеттеров/коллекций. Плюс, не забыть тонну хелперов для обновления отдельных узлов графа (аля person = person.WithName("XYZ") вместо person.Name = "XYZ").
Это реально адова работа, а биндить такой граф к UI — гадость ещё большая. Нужны очень веские причины, чтобы такой изврат делать.
S>>4. Вопрос очень похож на XY-problem. Давайте начнём с "Для чего тут readonly"? J>Ну убедили, давайте поиграем и в эту игру, в параллель.
Так оно работает Проблема у вас не в "нужно immutable object tree", а в "нужно подстраховаться от случайного двустороннего биндинга" плюс "нужно прикрутить undo-redo к произвольному графу объектов".
Первое делается на порядок проще — проверками в обработчике для INotifyPropertyChanged и/или выставлением IsReadOnly = true в стилях контролов.
Второе (undo-redo) надо очень аккуратно продумывать и закладывать в код с самого начала, особенно с учётом того, что данные могут меняться асинхронно (в фоновых операциях).
Основных вариантов всего два: или очередь из undo-redo-команд, или всё тот же граф immutable-объектов, но это в теории. На практике я не видел ни одного красивого удобного универсального решения. Вне зависимости от ЯП.
J>Разогрею: всё таки непонятно, как вы с этим языком работаете?
В Visual Studio обычно.
J>Вот, собрал модель данных: J>Пишу редактор модели. Биндю всё это к UI (WPF). J>Собственно всё что хочется, выдать клиенту это всё в readonly формате. А если UI меняет что-то, связывать это через явные вызовы сеттеров в модели. J>Не трогая пока сеттеры, как выдать этот класс UI-ю read only?
Очевидно, поменять его на структуру! Не вижу в этих классах ничего кроме данных — это не объекты, это структуры данных. Правда не помню, работает ли биндинг для полей структур, кажется он работает только для свойств классов. А для всех других требований структуры тут подходят — они будут копироваться, а не передаваться по ссылке.
Здравствуйте, johny5, Вы писали:
J>Ну убедили, давайте поиграем и в эту игру, в параллель. J>Я пишу UI редактор этой модели: в частности, добавить "игровую науку", убавить, изменить её значения. Я решил (вроде как по MVVM?) что будет у меня View class, который будет уметь отображать эту модель в редакторе. При переключении всяких табов или выбора подменю, UI должен сам уметь вытягивать нужные данные из модели и показывать на экране. При этом я хотел гарантировать, что UI (не доверяю WPF) случайно не смог изменить саму модель. С какимнть int переменной оно вполне сможет сбиндиться двусторонне. Ну или чтобы я сам впопыхах не впилил прямые изменения модели прямо в код.
J>Все изменения модели будут проходить через другой (ViewModel?) класс, оно будет подписываться на всякие нужные кнопки само и менять модель. Дополнительно ViewModel будет следить у меня за Undo/Redo, Save/Load и прочие.
Здравствуйте, johny5, Вы писали:
J>Разогрею: всё таки непонятно, как вы с этим языком работаете? J>Вот, собрал модель данных:
J> class ScienceModule
J> {
J> public int class_id; // ClassCategories
J> ..
J> ...
J> ... много ещё классов и полей
J>Пишу редактор модели. Биндю всё это к UI (WPF).
Очень интересно, расскажите-ка к какому ЮАЮ и как именно вы это так биндите, что у вас поля биндятся? Биндинг, мне кажется, по-дефолту, работает именно со свойствами, а поля игнорирует Тут или вы где-то лукавите или, простите, если я чего перепутал
J>Собственно всё что хочется, выдать клиенту это всё в readonly формате.
Легче лёгкого. Если интерфейс у вас работает только посредством биндинга, то отдавайте ему просто object с таким тайп-дескриптором, который не позволит через биндинг менять свойства.
J>А если UI меняет что-то, связывать это через явные вызовы сеттеров в модели.
А если всё-таки что-то когда-то менять таки нужно, то вы можете в своих дескрипторах это всё добро разрешить, перехватить команды интерфейса и сохранить в моделе любым способом.
J>Не трогая пока сеттеры, как выдать этот класс UI-ю read only?
Курите TypeDescriptor. Какая-то работа с биндингом без понимания этой кухни — это примерно как дать ребёнку поиграть с заряженным пистолетом.
есть пример того, как оно может работать: как свойство то делается ридонли, то возвращается обратно. В public override void SetValue пропертидескриптора можно отловить изменение значения свойства, но это не единственный способ, разберитесь с тем, как TypeDescriptor работает и что там есть и найдёте более подходящий для вашего случая способ.
Здравствуйте, johny5, Вы писали:
J>Пишу редактор модели. Биндю всё это к UI (WPF). J>Собственно всё что хочется, выдать клиенту это всё в readonly формате. А если UI меняет что-то, связывать это через явные вызовы сеттеров в модели.
Далее напишите ViewModel для вашей модели и для подмоделей. Это одна из задач ViewModel — обеспечивать целостность данных, чтобы какой-нибудь дизайнер, не особо разбирающией в нюансах, запилил View и не подпортил данные Model'и.
Модель
class Model
{
public List<ScienceModule> planet_blocks;
...
}
оборачивается в что-то вроде
class ViewModel: INotifyPropertyChanged
{
ObservableCollection<ViewModelScience> _planetBlocks;
public ObservableCollection<ViewModelScience> PlanetBlocks
{
get { return _planetBlocks; }
set
{
_planetBlocks = value;
OnPropertyChanged(); // [CallerMemberName]
}
}
public ObservableCollection<ViewModelScience> ScienceTree {get;} // только геттер, инстанциируется конструктором
public ObservableCollection<ViewModelScience> ShipBlocks {get; private set;} // может быть изменен во ViewModel, при этом необходимо вызвать OnPropertyChanged(nameof(ShipBlock)] самостоятельно
}
Все изменения с коллекциями производятся через методы ViewModel, например, команда CommandAddPlanet проверит на дупликаты и т.д. PlanetBlocks используется для биндинга к ItemsSource какого-либо контейнера.
Во View прописываются DataTemplate задающий отображение (другой View) для ViewModelScience.
Эта иерархия ViewModel и обеспечивает целостностью данных.
Спасибо за развёрнутый ответ. Хоть это уже и немного некропостинг.
Так по сути и сделал, единственно чтобы сэкономить себе пальцы и не писать под каждое проперти такой классик, наклепал его полиморфным через reflection, оно получает рефлекшн ссылку на поле и понеслась пляска.
Такой подход помог мне изящно впилить undo/redo, буквально в 10 строках. На OnPropertyChanged в модель складывались 2 undo/redo команды, которые просто заполняли редактируемое поле старым либо новым значением. Так изящно что я не удержусь и приведу весь его код прямо тут:
Конст действительно перестал быть необходимостью, хотя его наличие чуточку бы помогло добавить компайл-тайм проверок что я не оступился во ViewModel имплементации.