วิธีส่งผลกระทบต่อคุณสมบัติของวัตถุดั้งเดิมเมื่อตั้งค่าวัตถุหนึ่งให้เท่ากับวัตถุอื่น C#

การสร้างเกมข้อความเพื่อฝึกเขียนโค้ดและพบปัญหาที่แปลกประหลาดจริงๆ ฉันค้นหาสิ่งนี้ แต่คนส่วนใหญ่พยายามโคลนวัตถุหนึ่งไปยังอีกวัตถุหนึ่งในขณะที่ฉันพยายามชี้วัตถุ 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();
        }

นี่คือส่วนที่เกี่ยวข้องของโค้ดจากคลาสผู้เล่นของฉัน ผู้เล่น_ อะไรก็ตามที่เป็นของที่ผู้เล่นได้ติดตั้ง ไม่ว่าจะเป็นชุดเกราะ อาวุธ ฯลฯ ฉันใช้อินเทอร์เฟซ 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 ในโหมดแก้ไขจุดบกพร่อง ผมสามารถเห็นได้ว่าสิ่งนี้ Player_Weapon ความทนทานเปลี่ยนจาก 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 block ที่ว่างเปล่า มันมีกลิ่นนิดหน่อย   -  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