Home > OS >  C# - iterable data type with type safety and no duplication
C# - iterable data type with type safety and no duplication

Time:03-09

I am trying to find the best way to have a data collection with the following properties:

  • Immutable / Read-Only
  • Keyed Access rather than string access (ImmutableDictionary will not work, typos will only be exposed at runtime)
  • Enumerable (can iterate over)
  • Type enforcement of values

Some things I have looked at so far:

plain class:

class Foo
{
    public static readonly BarType Item1 = new SubTypeA(Scope: "Global", chickens: 4);
    public static readonly BarType Item2 = new SubTypeB(scope: "Local", chickens: 37);

    public static IEnumerable<BarType> AllItems()
    {
        yield Var1;
        yield Var2;
    }

    public static BarType fetch(int idx)
    {
        return AllItems().ToList()[idx]
    }
}

Problem with this approach is that there is a possibility that a dev could by mistake add a new Item3 but forget to add the explicit yield statement to AllItmes

Another approach I tried is using reflection:

class Foo
{
    public static readonly BarType Item1 = new SubTypeA(Scope: "Global", chickens: 4);
    public static readonly BarType Item2 = new SubTypeB(scope: "Local", chickens: 37);

    public static IEnumerable<BarType> AllItems()
    {
        return typeof(Foo).GetFields().Select(f => (BarType)f.GetValue(typeof(Foo)))
    }

    public static BarType Fetch(int idx)
    {
        return AllItems().ToList()[idx]
    }
}

Here the problem is the explicit cast. Adding another field which does not subclass BarType will again break at runtime.

I have not found a better solution, and am still using ImmuntableDictionary with string access.

Any suggestions? Speed is not a worry.


Answering questions

(1) Keyed acces is maybe not the best term. Maybe "dot access" is better?

I mean this Foo["bar"] -> the compiler cannot validate "bar"

Whereas Foo.bar will show an error if bar is not a field or method on Foo

A NamedTuple has this property, but is not iterable.

(2) ImmutableDictionary does not need string keys. What other keys could I use that would still enforce a 1:1 mapping (each key must match exactly one dictionary value)?

CodePudding user response:

If I understand your requirements right, you want to be able to enumerate over all fields of a specific type. I would create the list of fields once:

class Foo
{
    public static readonly BarType Item1 = new SubTypeA(Scope: "Global", chickens: 4);
    public static readonly BarType Item2 = new SubTypeB(scope: "Local", chickens: 37);

    private static List<BarType> _bars =
        typeof(Foo).GetFields()
                   .Where(f => f.FieldType==typeof(BarType)
                   .Select(f => (BarType)f.GetValue(typeof(Foo)))
                   .ToList();

    public static IEnumerable<BarType> AllItems()
    {
        return _bars;
    }

    public static BarType Fetch(int idx)
    {
        return _bars[idx];
    }
}

Now the only risk is that a developer adds a BarType after the _bars declaration since static members are initialized in the order that they appear in the class. You could mitigate that risk with a stern comment and a unit test that gets all BarType fields after initialization and compares it to AllItems().

CodePudding user response:

How about

public class SafeDict {

    public enum Keys {
          K1,K2,K3
    }
    private Dictionary<Keys, BarType> _dict = new Dictionary<Keys, BarType>() {
        {Keys.K1, new BarType() },
        {Keys.K2, new BarType() },
    };
    public BarType Get(Keys key) {
        return _dict[key];
    }
    public IEnumerable<BarType> GetAll() {
        return _dict.Values;
    }
}
  •  Tags:  
  • c#
  • Related