Search code examples
c#.netdictionarygetter-setter

Slicing a Dictionary in C#


I'm trying to create a class in C# that has a dictionary (bigDict) it gets from a JSON file. bigDict maps a string to a big data structure. From this, I want to create another dictionary (smallDict) that maps each string from the key to just one element (a string) in bigDict's data structure value.

I've tried to use get and set to create smallDict. I was able to successfully use get, but I'm stuck at set.

class myClass
{
    public Dictionary<string, SomeStruct> bigDict{ get; set; } /*get and set from a JSON file*/

    public virtual Dictionary<string, string> smallDict
    {
        get => bigDict.ToDictionary(x => x.Key, x => x.Value.ElemFromStruct);   //works fine

        set => smallDict.ToDictionary.update(key , value); //doesn't work fine
    }
}

As a result of not being able to set properly, I'm getting property or indexer cannot be assigned to, is readonly errors in some pre-existing test cases (I'm refactoring code)


Solution

  • Define a "one-off" type SmallDict:

        public SmallDict<string> smallDict;
    
        public myClass()
        {
            smallDict = new SmallDict<string>(bigDict);
        }
    
        class SmallDict<TKey>
        {
            public readonly Dictionary<TKey, SomeStruct> BigDict;
    
            public SmallDict(Dictionary<TKey, SomeStruct> bigDict)
            {
                BigDict = bigDict;
            }
    
            public string this[TKey key]
            {
                get => BigDict[key].ElemFromStruct;
                set {
                    var obj = BigDict[key];
                    obj.ElemFromStruct = value;
                    BigDict[key] = obj;
                }
            }
        }
    

    Use it as follows:

    Console.WriteLine(smallDict["key1"]); // Equivalent to printing bigDict[key].ElemFromStruct
    smallDict["key1"] = "new value";      // Equivalent to bigDict[key].ElemFromStruct = "new value"
    

    It's a mere wrapper around a dictionary, so if you want more methods than plain indexing, you'll have to do all the plumbing manually.

    Genericity

    Notice how SmallDict only works for dictionaries of SomeStruct...

    I would have written a generic DictionarySlice class, but any attempt at actual genericity is thwarted by C#'s rigid type system: there is no sastisfying way to genericly tell which property to slice on.

    Possible solutions:

    • Pass getters and setters as lambdas -- sure you can do that, you would have more code in your lambdas than in your actual class, mind you. It could be useful if you need to slice on many different properties.
    • Make SomeStruct implement IGetSetElemFromStruct which the generic class would use -- not terribly elegant and potentially extremely cumbersome if you have many properties to slice on.
    • Reflection -- included for completeness, but won't expand on it...

    Large structures

    Avoid large structs; and more importantly avoid mutable structs, which are universally considered a Very Bad Thing(TM) in C#. From Microsoft's design guidelines, emphasis mine:

    In general, structs can be very useful but should only be used for small, single, immutable values that will not be boxed frequently.

    set {
        var obj = BigDict[key];
        obj.ElemFromStruct = value;
        BigDict[key] = obj;
    }
    

    Notice that in the setter above, I get a copy of the struct, modify it, and copy it back to the dictionary... not great. If it's gonna be large, do yourself a favor and use a class. You would then simply write:

    set { BigDict[key].ElemFromStruct = value; }
    

    Non-trivial operations in properties get/set

    This is about the code you wrote as an example:

    public virtual Dictionary<string, string> smallDict
    {
        get => bigDict.ToDictionary(x => x.Key, x => x.Value.ElemFromStruct);   //works fine
    

    Avoid them as much as possible. There is no definite line saying what can go in properties getters/setters, but creating and populating a dictionary in a get is simply way too much by any reasonable standard: imagine if you had to doubt every property access.

    Maybe don't use this at all

    This solution is the best I could come up with, but it's clunky and not terribly useful. You have to judge for yourself depending on your use-case, but generally accessing/mutating bigDict[key].ElemFromStruct directly would be better.