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;
}
}