Как повлиять на свойство исходного объекта при установке одного объекта равным другому объекту С#

Делал текстовую игру для практики кодирования и столкнулся с действительно странной проблемой. Я искал это, но большинство людей пытаются клонировать один объект в другой, тогда как я пытаюсь указать объект B на объект A, потому что я хочу изменить свойства одного и того же объекта.

public Armor Player_Chest_Armor { get; set; }
public Armor Player_Head_Armor { get; set; }
public Armor Player_Legs_Armor { get; set; }
    public Weapon Player_Weapon { get; set; }
    public Spell Player_Spell { get; set; }
    public List<Consumable> Consumables { get; set; }
    public List<IEquipment> Inventory { get; set; }

    [JsonConstructor]
public Player (string name) {
        this.Name = name;
        this.Consumables = new List<Consumable>();
        this.Inventory = new List<IEquipment>();
        this.Inventory.Add(new Weapon("bronze sword", 22, 28, 25, 1.2, false));
        this.Inventory.Add(new Armor("bronze chestplate", Armor.ArmorSlot.Chest, 35, 10, 20, false));
        this.Inventory.Add(new Armor("bronze helmet", Armor.ArmorSlot.Head, 12, 3, 7, false));
        this.Inventory.Add(new Armor("bronze legplates", Armor.ArmorSlot.Legs, 20, 5, 10, false));
        this.Consumables.Add(new Consumable("minor health potion", 3, Consumable.PotionType.Health, 50));
        this.Consumables.Add(new Consumable("minor mana potion", 3, Consumable.PotionType.Mana, 50));
        this.Player_Spell = new Spell("Fireball", 50, 0, 1);
    }

    public void EquipInitialGear() {
        this.EquipWeapon(this.Inventory[0] as Weapon);
        this.EquipArmor(this.Inventory[1] as Armor);
        this.EquipArmor(this.Inventory[2] as Armor);
        this.EquipArmor(this.Inventory[3] as Armor);
    }
    public void DecreaseArmorDurability() {
        try {
            this.Player_Chest_Armor.DecreaseDurability();
        }

Это соответствующая часть кода из моего класса игрока. Player_ что угодно для вещей, которыми экипирован игрок, будь то доспехи, оружие и т. д. Я использую интерфейс IEquipment для соединения объектов доспехов и оружия в один список, поскольку инвентарь игрока должен включать все. Когда игрок создан, вызовы методов EquipWeapon и EquipArmor устанавливают эти объекты индекса инвентаря в соответствующий слот брони/оружия игрока. Код для этого ниже и для простоты предположим, что нет уже экипированного оружия, поэтому он переходит прямо к строке this.Player_Weapon = weapon.

    public void EquipWeapon(Weapon weapon) {
        if (weapon.IsEquipped() == true) {
            Console.WriteLine("You have already equipped {0}.", weapon.GetName());
            return;
        }
        try {
            if (this.Player_Weapon.Equipped == true) {
                this.UnequipWeapon(this.Player_Weapon);
            }
        }
        catch(NullReferenceException) {}
        this.Player_Weapon = weapon;
        weapon.Equipped = true;
        Console.WriteLine("You have equipped {0}.", this.Player_Weapon.GetName());
    }

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

    public void DecreaseDurability() {
        this.Durability -= 1;
    }

В каждом раунде атаки я вызываю этот метод, чтобы уменьшить прочность оружия на 1 из максимальных 100. В режиме отладки я вижу, где this.Player_Weapon.Durability меняется со 100 до 99. Однако, если я посмотрю на что должен быть тот же объект в инвентаре, его долговечность равна 100, что заставляет меня думать, что он создает копию объекта вместо ссылки на исходный объект.

Я убежден, что это проблема с указателем, но я просто не вижу, в чем проблема. Я хочу, чтобы изменения свойств Player_Weapon также влияли на изменения того же объекта этого оружия в инвентаре, на который он должен указывать.

Отредактировано для ясности: игрок, оружие и доспехи — это все классы, а не структуры.


person Tim Thompson    schedule 20.12.2019    source источник
comment
см. ключевое слово ref.   -  person    schedule 20.12.2019
comment
Weapon это class, а не struct, верно?   -  person Fildor    schedule 20.12.2019
comment
Да, это класс, поэтому он должен быть по ссылке, а не по значению.   -  person Tim Thompson    schedule 20.12.2019
comment
упрощенный пример работал для object_b, ссылающегося на object_a, что не совсем правильно. object_b не ссылается на object_a. У вас могут быть переменные, которые содержат ссылку на один и тот же объект. Но они не ссылаются друг на друга.   -  person Fildor    schedule 20.12.2019
comment
Я не вижу ничего очевидного в этом коде. Player.Player_Weapon должен ссылаться на тот же экземпляр Weapon, что и Player.Inventory[0], как вы говорите. Должно быть что-то еще, где-то еще.   -  person Fildor    schedule 20.12.2019
comment
Несвязанный: я бы сделал нулевую проверку или использовал Null-Object-Pattern вместо try/catch(NRE) с пустым блоком catch. Это немного пахнет.   -  person Fildor    schedule 20.12.2019
comment
О, о, о... секундочку. Player_Weapon является r/w, а должен быть доступен только для чтения. Единственным местом, где должно быть разрешено устанавливать свойство Player_Weapon, должен быть метод EquipWeapon. Сделайте его {get; private set;} и посмотрите, где он сломается.   -  person Fildor    schedule 20.12.2019
comment
Инвентарь тоже общедоступен... еще один вектор атаки... Вы должны убедиться, что всякий раз, когда элемент инвентаря заменяется, вы обновляете свойство. Возможно, у вас вообще не должно быть такого свойства Player_Weapon вообще. Может быть, просто сохраните индекс в инвентаре, отметив предмет, который является оружием игрока. Но я забегаю вперед...   -  person Fildor    schedule 20.12.2019
comment
Я посмотрю комментарии сегодня вечером после работы, но мне нравятся идеи. Рассмотрю нулевую проверку... Я хочу сделать код как можно более чистым. Я думаю, что ваше решение просто хранить индекс элемента вместо установки объекта равным другому объекту звучит как хорошее исправление. Просто хотел бы я знать, в чем проблема, лол.   -  person Tim Thompson    schedule 20.12.2019


Ответы (1)


Это файл сохранения игры в формате JSON. Причина, по которой эта ошибка была настолько коварной, что я обратился за помощью к SO, заключается в том, что она работает нормально, но когда я выхожу из игры, которая создает автосохранение в JSON, она сохраняет данные объекта. Однако Player_Weapon и любой Player_Armor являются указателями класса на предмет в инвентаре. Это работает нормально, пока программа работает. После создания JSON и сохранения данных кажется, что экземпляры класса Player отделены от инвентаря. Вот что сохраняется в файле:

"Player_Chest_Armor": {
    "Name": "bronze chestplate",
    "ArmorCategory": 1,
    "ItemValue": 35,
    "ArmorRating": 12,
    "Equipped": true,
    "Durability": 98
  },
  "Player_Head_Armor": {
    "Name": "bronze helmet",
    "ArmorCategory": 0,
    "ItemValue": 12,
    "ArmorRating": 6,
    "Equipped": true,
    "Durability": 98
  },
  "Player_Legs_Armor": {
    "Name": "bronze legplates",
    "ArmorCategory": 2,
    "ItemValue": 20,
    "ArmorRating": 8,
    "Equipped": true,
    "Durability": 98
  },
  "Player_Weapon": {
    "Name": "notched axe",
    "RegDamage": 16,
    "ItemValue": 10,
    "CritMultiplier": 1.2,
    "Equipped": true,
    "Durability": 98
  },
  "Player_Spell": {
    "Name": "Fireball",
    "ManaCost": 50,
    "Level": 1,
    "SpellCategory": 0,
    "FireOffense": {
      "BlastDamage": 30,
      "BurnDamage": 5,
      "BurnCurRounds": 3,
      "BurnMaxRounds": 3
    }
  },
  "Consumables": [
    {
      "Name": "minor health potion",
      "Quantity": 1,
      "ItemValue": 3,
      "Equipped": false,
      "PotionCategory": 0,
      "RestoreHealth": {
        "RestoreHealthAmt": 50
      }
    },
    {
      "Name": "minor mana potion",
      "Quantity": 1,
      "ItemValue": 3,
      "Equipped": false,
      "PotionCategory": 1,
      "RestoreMana": {
        "RestoreManaAmt": 50
      }
    }
  ],
  "Inventory": [
    {
      "$type": "DungeonGame.Weapon, DungeonGame",
      "Name": "bronze sword",
      "RegDamage": 27,
      "ItemValue": 25,
      "CritMultiplier": 1.2,
      "Equipped": false,
      "Durability": 98
    },
    {
      "$type": "DungeonGame.Armor, DungeonGame",
      "Name": "bronze chestplate",
      "ArmorCategory": 1,
      "ItemValue": 35,
      "ArmorRating": 12,
      "Equipped": true,
      "Durability": 98
    },
    {
      "$type": "DungeonGame.Armor, DungeonGame",
      "Name": "bronze helmet",
      "ArmorCategory": 0,
      "ItemValue": 12,
      "ArmorRating": 6,
      "Equipped": true,
      "Durability": 98
    },
    {
      "$type": "DungeonGame.Armor, DungeonGame",
      "Name": "bronze legplates",
      "ArmorCategory": 2,
      "ItemValue": 20,
      "ArmorRating": 8,
      "Equipped": true,
      "Durability": 98
    },
    {
      "$type": "DungeonGame.Weapon, DungeonGame",
      "Name": "notched axe",
      "RegDamage": 16,
      "ItemValue": 10,
      "CritMultiplier": 1.2,
      "Equipped": true,
      "Durability": 98
    }
  ]
}

Как видно из данных в файле сохранения игры, нет ссылки на предмет инвентаря для данных класса оружия/брони игрока, поэтому, когда игра перезагружает данные, она отделяет эти объекты от исходных объектов в инвентаре. Для оружия/брони игрока установлены эти значения, но они больше не ссылаются на экземпляры этих классов в инвентаре, поэтому создается дубликат объекта.

Код сохраненной игры создает файл JSON посредством сериализации. Из https://www.newtonsoft.com/json/help/html/PreserveObjectReferences.htm, «По умолчанию Json.NET сериализует все встречающиеся объекты по значению».

Решение сохранить ссылку, по-видимому, заключается в объявлении и инициализации json с использованием следующего кода:

string json = JsonConvert.SerializeObject(people, Formatting.Indented,
new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });   

Раньше я не использовал PreserveReferencesHandling, что вызывало эту ошибку.

person Tim Thompson    schedule 21.12.2019