1

Assume I have a struct Point with public int's x and y. I want to change a value and I can. But once I store it in a dictionary, I can no longer do so. Why?

MWE:

using System;
using System.Collections.Generic;

public class Program
{
    public struct Point
    {
        public int x, y;

        public Point(int p1, int p2)
        {
            x = p1;
            y = p2;
        }
    }

    public static void PrintPoint(Point p)
    {
        Console.WriteLine(String.Format("{{x: {0}, y: {1}}}", p.x, p.y));
    }

    public static void Main()
    {
        var c = new Point(5, 6);
        PrintPoint(c);                                          // {x: 5, y: 6}

        c.x = 4;  // this works
        PrintPoint(c);                                          // {x: 4, y: 6}

        var d = new Dictionary<string, Point>() 
        {
            { "a", new Point(10, 20) },
            { "b", new Point(30, 40) }
        };

        foreach (Point p in d.Values)
        {
            PrintPoint(p);  // this works
        }

        PrintPoint(d["a"]); // this works                       // {x: 10, y: 20}

        Console.WriteLine(d["a"].x.ToString());  // this works  // 10

        // d["a"].x = 2; // why doesn't this work?
    }
}

How come I can access the struct variables when it's in a dictionary but can no longer change them? How do I change them?

9
  • 3
    Please see: stackoverflow.com/questions/441309/why-are-mutable-structs-evil Commented May 18, 2018 at 20:23
  • 3
    Because d["a"] returns a temporary copy that you aren't storing anywhere. Modifying a temporary wouldn't make sense Commented May 18, 2018 at 20:23
  • It "does not work" because the compiler complains. The text of that complaint is relevant. Commented May 18, 2018 at 20:49
  • 1
    One way to solve this is to assign a new Point with the new x value and the same y value to the dictionary item: d["a"] = new Point(2, d["a"].y); Commented May 18, 2018 at 21:01
  • One comment from a design point of view: If you're going to have a constructor that takes values that are used to set properties (or fields in this case), it's helpful to the clients of your class/struct to make the constructor argument names the same as the property names so they understand what they're setting. For example: public Point(int x, int y) { ... } Commented May 18, 2018 at 21:03

2 Answers 2

2

There are a couple things wrong with what you're trying to do.

In 99.999999999% of cases, structs should be readonly. See Microsoft design guidelines https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/struct

X DO NOT define mutable value types.

Mutable value types have several problems. For example, when a property getter returns a value type, the caller receives a copy. Because the copy is created implicitly, developers might not be aware that they are mutating the copy, and not the original value. Also, some languages (dynamic languages, in particular) have problems using mutable value types because even local variables, when dereferenced, cause a copy to be made.

You should also not mutate an object that is being used as a dictionary key. Dictionaries are based on hash values, and if you change a field of the key, you will change its hash value. See: https://stackoverflow.com/a/3007334/4415493

The Dictionary type makes no attempt to protect against the user modifying the key used. It is purely left up to the developer to be responsible in not mutating the key.

If you think about this a bit this is really the only sane route that Dictionary can take. Consider the implication of doing an operation like a memberwise clone on the object. In order to be thorough you'd need to do a deep clone because it will be possible for an object referenced in the key to also be mutated and hence affect the hash code. So now every key used in the table has it's full object graph cloned in order to protect against mutation. This would be both buggy and possibly a very expensive operation.

Sign up to request clarification or add additional context in comments.

5 Comments

Unless I missed something, the OP is not mutating a dictionary key, are they?
No, but the OP is trying to change the item stored under that key - and there he gets a copy
@HansKesting Right, but half of this answer is about not mutating an object used as a key...
First part makes sense (+1), but I am not trying to change the key.
I think my question should be closed as a duplicate, but I'm going to accept this for now because the OCD part of me wants to see 1991 points become 2,000 :P - thanks for help for real though!
1

Your dictionary value is a Point object. So, you can update the key with a new Point object.

Right Way (Update):
            d["a"]= new Point(5, 20);
            PrintPoint(d["a"]);
            Console.WriteLine(d["a"].x.ToString());

Round About: (Remove and Add)
            d.Remove("a");
            d.Add("a", new Point(2, 20));
            PrintPoint(d["a"]);
            Console.WriteLine(d["a"].x.ToString()); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.