patterncsharpMinor
Constant keys thread safe dictionary
Viewed 0 times
constantkeysthreadsafedictionary
Problem
I find that I use this pattern a lot where I have a dictionary which I would only read from and update and wouldn't add/remove keys.
In that case, using
I wrote a simple class which implements such dictionary by using a wrapper class as its values:
```
class ConstantKeysDictionary : IDictionary where TValue : class
{
#region Internal Classes
class Wrapper
{
private TValue m_Val;
public TValue Value
{
get
{
return Volatile.Read(ref m_Val);
}
set
{
Volatile.Write(ref m_Val, value);
}
}
}
#endregion
private Dictionary m_InnerDictionary;
public ConstantKeysDictionary(IDictionary toCopy)
{
m_InnerDictionary = new Dictionary();
foreach (var pair in toCopy)
{
m_InnerDictionary.Add(pair.Key, new Wrapper() { Value = pair.Value });
}
}
public ConstantKeysDictionary(IEnumerable keys)
{
m_InnerDictionary = new Dictionary();
foreach (var key in keys)
{
m_InnerDictionary.Add(key, new Wrapper());
}
}
public ConstantKeysDictionary(Action> builder)
{
Dictionary dict = new Dictionary();
builder(dict);
m_InnerDictionary = new Dictionary();
foreach (var pair in dict)
{
m_InnerDictionary.Add(pair.Key, new Wrapper() { Value = pair.Value });
}
}
public TValue this[TKey key]
{
get
{
return m_InnerDictionary[key].Value;
}
set
{
if (!m_InnerDictionary.ContainsKey(key))
{
In that case, using
ConcurrentDictionary is not necessary and could hinder performance.I wrote a simple class which implements such dictionary by using a wrapper class as its values:
```
class ConstantKeysDictionary : IDictionary where TValue : class
{
#region Internal Classes
class Wrapper
{
private TValue m_Val;
public TValue Value
{
get
{
return Volatile.Read(ref m_Val);
}
set
{
Volatile.Write(ref m_Val, value);
}
}
}
#endregion
private Dictionary m_InnerDictionary;
public ConstantKeysDictionary(IDictionary toCopy)
{
m_InnerDictionary = new Dictionary();
foreach (var pair in toCopy)
{
m_InnerDictionary.Add(pair.Key, new Wrapper() { Value = pair.Value });
}
}
public ConstantKeysDictionary(IEnumerable keys)
{
m_InnerDictionary = new Dictionary();
foreach (var key in keys)
{
m_InnerDictionary.Add(key, new Wrapper());
}
}
public ConstantKeysDictionary(Action> builder)
{
Dictionary dict = new Dictionary();
builder(dict);
m_InnerDictionary = new Dictionary();
foreach (var pair in dict)
{
m_InnerDictionary.Add(pair.Key, new Wrapper() { Value = pair.Value });
}
}
public TValue this[TKey key]
{
get
{
return m_InnerDictionary[key].Value;
}
set
{
if (!m_InnerDictionary.ContainsKey(key))
{
Solution
Principle of least astonishment
Since the
The class' signature would then become:
This will allow you to remove all the unnecessary methods and thus prevent the confusion and desire to use for example the
By doing so you follow the Principle of least astonishment
If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature.
and further:
In general engineering design contexts, the principle can be taken to mean that a component of a system should behave in a manner consistent with how users of that component are likely to expect it to behave; that is, users should not be astonished at the way it behaves.
(emphasis mine)
Your dictionary doesn't behave this way.
Of course you still can use internally whatever data structure you want but you should provide a public API that does exacly what is expected from it, nothing more and nothing less and especially doesn't throw any unexpected exception.
Liskov substitution principle
As @RJFalconer mentioned in his comment your class also vialotes the Liskov substitution principle.
Substitutability is a principle in object-oriented programming that states that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., an object of the type T may be substituted with its subtype object of the type S) without altering any of the desirable properties of that program (correctness, task performed, etc.).
This means that you should be able to use your dictionary everywhere the
Since the
ConstantKeysDictionary isn't a real dictionary and does not behave this way - it throws many exceptiosn for known operations. It would be safer from the user point of view to call it a collection and implement it like one.The class' signature would then become:
class ConstantKeyValueCollection : IEnumerable> where TValue : classThis will allow you to remove all the unnecessary methods and thus prevent the confusion and desire to use for example the
Remove. I'd rather don't have it then wonder why it throws an exception and having to look for it in some documentation.By doing so you follow the Principle of least astonishment
If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature.
and further:
In general engineering design contexts, the principle can be taken to mean that a component of a system should behave in a manner consistent with how users of that component are likely to expect it to behave; that is, users should not be astonished at the way it behaves.
(emphasis mine)
Your dictionary doesn't behave this way.
Of course you still can use internally whatever data structure you want but you should provide a public API that does exacly what is expected from it, nothing more and nothing less and especially doesn't throw any unexpected exception.
Liskov substitution principle
As @RJFalconer mentioned in his comment your class also vialotes the Liskov substitution principle.
Substitutability is a principle in object-oriented programming that states that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., an object of the type T may be substituted with its subtype object of the type S) without altering any of the desirable properties of that program (correctness, task performed, etc.).
This means that you should be able to use your dictionary everywhere the
IDictionary interface is acceptable (your type is a subtype) and don't experience any side effects. But you cannot do this. It could throw exceptions all over the place.Code Snippets
class ConstantKeyValueCollection<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>> where TValue : classContext
StackExchange Code Review Q#145774, answer score: 5
Revisions (0)
No revisions yet.