1

is it possible to store Class values into a dictionary?

Let me clarify the question a bit. For the sake of simplicity I'm reducing the amount of variables that I would like to store (otherwise I should have used a Struct if I've understood correctly); I'm using Unity and in my project I have:

1 Inputfield to let the user add a profile name; 1 Dropdown that stores the profiles created by the user; 2 more Inputfields that the user fills out with infos such as Age and Car's Color;

I thought I could store every profile's variable associated with a person inside a dictionary so, after googling a bit, I've come out with something like this:

using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;


[System.Serializable]
public class TestClass
{
    public string age;
    public string carColor;

    public TestClass()
    {
        this.age = "";
        this.carColor = "";
    }
}

public class TestScript : MonoBehaviour
{

    public TMP_InputField profileName;
    public TMP_InputField age;
    public TMP_InputField carColor;
    public TMP_Dropdown drop;
    public Button saveProfile;
    public TestClass myTestClass;
    public Dictionary<string, TestClass> dictionary = new();

    void Start()
    {
        dictionary = new Dictionary<string, TestClass>();
        myTestClass = new TestClass();

        saveProfile.onClick.AddListener(AddProfile);
        drop.onValueChanged.AddListener(delegate{ DropdownUpdateValuesShown();});
    }

    void AddProfile()
    {
        if (!dictionary.ContainsKey(profileName.text))
        {
            SetClasseValues();
            dictionary.Add(profileName.text, myTestClass);
            drop.options.Add(new TMP_Dropdown.OptionData() { text = profileName.text });
        }
    }

    void SetClasseValues()
    {
        dictionary[drop.options[drop.value].text] = myTestClass;
        myTestClass.age = age.text;
        myTestClass.carColor = carColor.text;
    }

    void DropdownUpdateValuesShown()
    {
        myTestClass = dictionary[drop.options[drop.value].text];
        age.text = myTestClass.age;
        carColor.text = myTestClass.carColor;
    }
}

My goal is to store the values of each profile in the dropdown menu and let the user be able to save them via button and recall them selecting the desired dropdown option. The problem is that, even though I'm able to save them, when I select a different profile in the dropdown, the values shown are the last saved. I'm not able (since I'm quite a noob) to understand if it saves them correctly but it doesn't show the corrent ones of it only save and consequentially updates the last inserted in the input fiels.

I'm able to do so with two values by using a Dictionary and a single string output from the Inputfield but with multiple values I struggle. Maybe I've misunderstood the Class concept.

Thanks in advance for your patience and help.

Martyx
  • 41
  • 4

1 Answers1

2

What is happening is a misunderstanding of what is a reference or value type.

Very, very, very briefly (more details in the link below). Value type is something that you write the value and directly access that value. Example: bool, int, float, decimal, double, short, byte, char, struct.

Reference type you have the address to access the data of that object. Example: class.

I'll explain to you what's going on.

  1. You create an instance of your class in the constructor.

  2. The user enters this information below and clicks on "Save profile":

    Profile: Lamborghini Gallardo, Car color: Yellow, Age: 2

  3. The instance of the myTestClass object has its fields changed to what the user entered:

    age = 2, carColor = Yellow

  4. An item is added to the dictionary pointing to the myTestClass object instance.

  5. The user enters this information below and clicks "Save profile":

    Profile: Ferrari, Car color: Red, Age: 3

  6. The instance of the myTestClass object has its fields changed to what the user entered:

    age = 3, carColor = Red

  7. A NEW item is added to the dictionary, BUT pointing to the SAME myTestClass object instance.

What is happening is that you have only one instance of TestClass for your class TestScript and it always has the data of the last value you store.

An example of what's going on

When someone suggested you use a struct, it's because structs are value types and this "problem" does not happen.

To solve this, just create a new instance each time you need new profiles. I took the opportunity to change the code to avoid runtime errors:

[System.Serializable]
public class TestClass
{
    public string age = string.Empty;
    public string carColor = string.Empty;
}

public class TestScript : MonoBehaviour
{
    public TMP_InputField profileName;
    public TMP_InputField age;
    public TMP_InputField carColor;
    public TMP_Dropdown drop;
    public Button saveProfile;
    public Dictionary<string, TestClass> dictionary = new();

    void Start()
    {
        saveProfile.onClick.AddListener(AddProfile);
        drop.onValueChanged.AddListener(_ => DropdownUpdateValuesShown());
    }

    void AddProfile()
    {
        if (dictionary.ContainsKey(profileName.text))
        {
            // no-op and removing nested code
            return;
        }

        SetClasseValues();
        drop.options.Add(new TMP_Dropdown.OptionData { text = profileName.text });
    }

    void SetClasseValues()
    {
        var myTestClass = new TestClass
        {
            age = age.text,
            carColor = carColor.text,
        };

        dictionary.Add(profileName.text, myTestClass);
    }

    void DropdownUpdateValuesShown()
    {
        if (!dictionary.TryGetValue(drop.options[drop.value].text, out var testClass))
        {
            // Value not found
            return;
        }

        age.text = testClass.age;
        carColor.text = testClass.carColor;
        profileName.text = drop.options[drop.value].text;
    }
}

Take some time to read a bit about value and reference types: What is the difference between a reference type and value type in c#?

Pedro Paulo
  • 390
  • 3
  • 14