I am writing a custom observable collection for some of my needs and I want it to allow EditItem when used within a WPF DataGrid.
(see the code of the collection at the end of the question to avoid polluting the reading of the question)
You will see that I thought of 3 solutions. But I cannot get any of them working for now.
Solution 1
According to this question, if I want my ObservableDictionary to allow EditItem, it has to implement the non-generic IList. But as I am using a IDictionary as backing field to store the elements, it is impossible to properly implement the IList interface (because it is based on ordered index).
Unless someone find a way?
Solution 2
Next idea, instead of letting the system chose the CollectionView implementation, I can force it to use my own, like this:
<CollectionViewSource
x:Key="FooesSource"
Source="{Binding Fooes}"
CollectionViewType="local:MyCollectionView" />
To do so, I tried to override one existing CollectionView, one allowing EditItem, to suit my needs. For example:
class ObservableDictionaryCollectionView : ListCollectionView
{
public ObservableDictionaryCollectionView(IDictionary dictionary)
: base(dictionary.Values)
{
}
}
But it does not work because dictionary.Values is a ICollection and ICollection does not implement IList (it is the opposite -_-).
Is there another built-in CollectionView that could suit my needs?
Solution 3
Based on the next idea, I could try to write my own, from scratch, CollectionView. But before doing this, I would like to know:
If anyone has a better idea?
If it is possible?
May be some dictionary constraints made this impossible? Which interfaces should I implement? (IEditableCollectionView, IEditableCollectionViewAddNewItem, IEditableCollectionView, ICollectionViewLiveShaping, etc)
As promised, here is the code of the collection:
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IEnumerable<TValue>, INotifyCollectionChanged
{
#region fields
private IDictionary<TKey, TValue> _innerDictionary;
#endregion
#region properties
public int Count { get { return _innerDictionary.Count; } }
public ICollection<TKey> Keys { get { return _innerDictionary.Keys; } }
public ICollection<TValue> Values { get { return _innerDictionary.Values; } }
public bool IsReadOnly { get { return false; } }
#endregion
#region indexors
public TValue this[TKey key]
{
get { return _innerDictionary[key]; }
set { this.InternalAdd(new KeyValuePair<TKey, TValue>(key, value)); }
}
#endregion
#region events
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region constructors
public ObservableDictionary()
{
_innerDictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(int capacity)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_innerDictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
#endregion
#region public methods
public bool ContainsKey(TKey key)
{
return _innerDictionary.ContainsKey(key);
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _innerDictionary.Contains(item);
}
public void Add(TKey key, TValue value)
{
this.InternalAdd(new KeyValuePair<TKey, TValue>(key, value));
}
public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> items)
{
if (!items.Any())
{
return;
}
var added = new List<TValue>();
var removed = new List<TValue>();
foreach (var item in items)
{
TValue value;
if (_innerDictionary.TryGetValue(item.Key, out value))
{
removed.Add(value);
}
added.Add(item.Value);
_innerDictionary[item.Key] = item.Value;
}
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, null));
if (removed.Count > 0)
{
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, removed));
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this.InternalAdd(item);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _innerDictionary.TryGetValue(key, out value);
}
public bool Remove(TKey key)
{
return this.InternalRemove(key);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return this.InternalRemove(item.Key);
}
public void Clear()
{
_innerDictionary.Clear();
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_innerDictionary.CopyTo(array, arrayIndex);
}
public IEnumerator<TValue> GetEnumerator()
{
return Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return _innerDictionary.GetEnumerator();
}
#endregion
#region private methods
/// <summary>
/// Adds the specified value to the internal dictionary and indicates whether the element has actually been added. Fires the CollectionChanged event accordingly.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private void InternalAdd(KeyValuePair<TKey, TValue> item)
{
IList added = new TValue[] { item.Value };
TValue value;
if (_innerDictionary.TryGetValue(item.Key, out value))
{
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, new TValue[] { value }));
}
_innerDictionary[item.Key] = item.Value;
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, null));
}
/// <summary>
/// Remove the specified key from the internal dictionary and indicates whether the element has actually been removed. Fires the CollectionChanged event accordingly.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private bool InternalRemove(TKey key)
{
TValue value;
if (_innerDictionary.TryGetValue(key, out value))
{
_innerDictionary.Remove(key);
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, new TValue[] { value }));
}
return value != null;
}
#endregion
}
You could build an ObservableCollection that implements both Dictionary and List, by doubly storing the inserted data into an internal list and a dictionary. The Dictionary provides the advantages of a dictionary, the List providing the better support for Binding Operations.
Disadvantages include doubling up your memory usage, and hurting your item removal time*
* Inserts** and Accesses have the same efficiency as a regular dictionary, but removal has to find the item in the internal list as well
** I guess inserts can also be more costly on the occasions they require array resizes.
Related
I know there are a lots of questions (1, 2, 3, 4, 5, etc) about that error, but I can't find one that explains the cause of this error and suitable for my case. Let me know if I miss one!
First of all, I am binding to my DataGrid ItemsSource with a custom class (not ObservableCollection or any other .NET built-in observable collection). Before showing you its code, let me explain how I thought of it (my assumptions are may be be wrong).
In my mind, to be bindable, a collection must implements at least IEnumerable and INotifyCollectionChanged. IEnumerable in order to the view to get the items to display (thanks to the GetEnumerator method) and INotifyCollectionChanged in order to the view to know the changes on the collection.
So I end up with this class:
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IEnumerable<TValue>, INotifyCollectionChanged
{
#region fields
private IDictionary<TKey, TValue> _innerDictionary;
#endregion
#region properties
public int Count { get { return _innerDictionary.Count; } }
public ICollection<TKey> Keys { get { return _innerDictionary.Keys; } }
public ICollection<TValue> Values { get { return _innerDictionary.Values; } }
public bool IsReadOnly { get { return false; } }
#endregion
#region indexors
public TValue this[TKey key]
{
get { return _innerDictionary[key]; }
set { this.InternalAdd(new KeyValuePair<TKey, TValue>(key, value)); }
}
#endregion
#region events
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region constructors
public ObservableDictionary()
{
_innerDictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(int capacity)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_innerDictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
#endregion
#region public methods
public bool ContainsKey(TKey key)
{
return _innerDictionary.ContainsKey(key);
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _innerDictionary.Contains(item);
}
public void Add(TKey key, TValue value)
{
this.InternalAdd(new KeyValuePair<TKey, TValue>(key, value));
}
public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> items)
{
if (!items.Any())
{
return;
}
var added = new List<TValue>();
var removed = new List<TValue>();
foreach (var item in items)
{
TValue value;
if (_innerDictionary.TryGetValue(item.Key, out value))
{
removed.Add(value);
}
added.Add(item.Value);
_innerDictionary[item.Key] = item.Value;
}
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, null));
if (removed.Count > 0)
{
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, removed));
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this.InternalAdd(item);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _innerDictionary.TryGetValue(key, out value);
}
public bool Remove(TKey key)
{
return this.InternalRemove(key);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return this.InternalRemove(item.Key);
}
public void Clear()
{
_innerDictionary.Clear();
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_innerDictionary.CopyTo(array, arrayIndex);
}
public IEnumerator<TValue> GetEnumerator()
{
return Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return _innerDictionary.GetEnumerator();
}
#endregion
#region private methods
/// <summary>
/// Adds the specified value to the internal dictionary and indicates whether the element has actually been added. Fires the CollectionChanged event accordingly.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private void InternalAdd(KeyValuePair<TKey, TValue> item)
{
IList added = new TValue[] { item.Value };
TValue value;
if (_innerDictionary.TryGetValue(item.Key, out value))
{
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, new TValue[] { value }));
}
_innerDictionary[item.Key] = item.Value;
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, null));
}
/// <summary>
/// Remove the specified key from the internal dictionary and indicates whether the element has actually been removed. Fires the CollectionChanged event accordingly.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private bool InternalRemove(TKey key)
{
TValue value;
if (_innerDictionary.TryGetValue(key, out value))
{
_innerDictionary.Remove(key);
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, new TValue[] { value }));
}
return value != null;
}
#endregion
}
It implements implicitly the IEnumerable<TValue>.GetEnumerator and explicitly the others GetEnumerator methods (IDictionary and IEnumerable) in order to my view display only the values of my dictionary, and I map the add/remove methods around the invocation of the CollectionChanged event.
My ViewModel is defined like this:
class MyViewModel
{
public ObservableDictionary<string, Foo> Foos { get; private set; }
public MyViewModel()
{
this.Foos = new ObservableDictionary<string, Foo>();
}
}
And bind it to my view like this:
<DataGrid ItemsSource="{Binding Facts}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" Width="*" />
<DataGridTextColumn Header="Type" Binding="{Binding Type}" IsReadOnly="True" Width="*" />
<DataGridTextColumn Header="Value" Binding="{Binding Value}" IsReadOnly="False" Width="*" />
</DataGrid.Columns>
</DataGrid>
Then, when I try to edit the Value, I get the specified error:
'EditItem' is not allowed for this view
When I put some breakpoints in my code, I never reach the ObservableDictionary indexor setter nor Foo.Value setter.
Are my thoughts about how the view gets the item from the binded collection corrects? Why am I getting this error and/or how can I authorize my view to EditItem?
Your source collection type (ObservableDictionary<TKey, TValue>) should implement the IList interface if you want to be able to edit the data in a DataGrid.
Whenever you bind to some collection property in WPF, you are always binding to an automatically generated view and not to the actual source collection itself.
The type of view that is created for you by the runtime depends on the type of the source collection and your source collection must implement the non-generic IList interface for the internal editing functionality of the DataGrid control to work.
I'm completely new to C#, so I'm about to make a horrible attempt at my own version of an OrderedDictionary unless someone can suggest an alternative.
I need to be able to access my elements by array index, retaining the order they were added, and I also will be frequently updating individual elements using their key.
Is there a collection that allows this on the phone?
If I keep a List and Dictionary will they both be pointing to the same item or is there some kind of pointer thing I have to do?:
Item i = new Item();
list.Add(i);
dict.Add("key", i);
Here's my implementation (comes from the open source OpenNETCF Extensions library):
public class OrderedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
{
private Dictionary<TKey, TValue> m_dictionary;
private List<TValue> m_list = new List<TValue>();
private object m_syncRoot = new object();
public OrderedDictionary()
{
m_dictionary = new Dictionary<TKey, TValue>();
}
public OrderedDictionary(IEqualityComparer<TKey> comparer)
{
m_dictionary = new Dictionary<TKey, TValue>(comparer);
}
public void Add(TKey key, TValue value)
{
lock (m_syncRoot)
{
m_dictionary.Add(key, value);
m_list.Add(value);
}
}
public TValue this[int index]
{
get { return m_list[index]; }
}
public TValue this[TKey key]
{
get { return m_dictionary[key]; }
}
public int Count
{
get { return m_dictionary.Count; }
}
public Dictionary<TKey, TValue>.KeyCollection Keys
{
get { return m_dictionary.Keys; }
}
public Dictionary<TKey, TValue>.ValueCollection Values
{
get { return m_dictionary.Values; }
}
public void Clear()
{
lock (m_syncRoot)
{
m_dictionary.Clear();
m_list.Clear();
}
}
public bool ContainsKey(TKey key)
{
return m_dictionary.ContainsKey(key);
}
public bool ContainsValue(TValue value)
{
return m_dictionary.ContainsValue(value);
}
public void Insert(int index, TKey key, TValue value)
{
lock (m_syncRoot)
{
m_list.Insert(index, value);
m_dictionary.Add(key, value);
}
}
public void Remove(TKey key)
{
lock (m_syncRoot)
{
if (ContainsKey(key))
{
var existing = m_dictionary[key];
m_list.Remove(existing);
m_dictionary.Remove(key);
}
}
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return m_dictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Using a List and a Dictionary is probably a good option actually. The "pointer thing" that you're talking about happens by default for Objects in .NET (any class and/or structure). All objects in .NET are passed around by reference.
So, if you use:
Item i = new Item();
list.Add(i);
dict.Add("key",i);
Console.WriteLine(list.Last() == dict["key"]);
Your output will be "true".
Best of luck!
I won't suggest using OrderedDictionary, since it's a non-generic container.
However, if you just want to use it like always. You can port Mono's version of OrderedDictionary.
https://github.com/mono/mono/blob/master/mcs/class/System/System.Collections.Specialized/OrderedDictionary.cs
Here's some tips if you want to port this:
Remove any unavailable interface
Remove serialization-related code
Replace ArrayList with List<object>
Replace Hashtable with Dictionary<object, object>
Each key is unique in the list. When a new key-value pair arrives, the pair is inserted into the list in the ascending order of value (if key already exists then updates the value).
Please avoid sorting the list for every insertion.
I would suggest SortedDictionary or SortedList
As per MSDN :
SortedList uses less memory than SortedDictionary.
SortedDictionary has faster insertion and removal
operations for unsorted data: O(log n) as opposed to O(n) for
SortedList.
Update : After comments
You will have to order the value by yourself for e.g using a dictioanry
var dictionary = new Dictionary<int, string>{ {1, "Z"}, {2, "A"}};
IOrderedEnumerable<KeyValuePair<int, string>> orderedEnumerable = dictionary.OrderBy(d => d.Value);
You aren't going to get a built in component with this behaviour, it's too non-standard. I'd be looking at why and when I needed these competing behaviours. Effectively you are looking at an alternate key. Short of just writing some for of linked list, off the top of my head, I'd look at SortedList for the by value part of it, and a Dictionary for key.
e.g.
a Dictionary of CustomerID and SortKey and a SortedList of SortKey and value.
I'd try and avoid it if I could on the baiss that maintaining both would cost more than simply returning a list of values in the required order on those occasions when you needed it.
If sorting the items for every enumeration is acceptable, you can use a Dictionary<TKey, TValue> and order the key-value pairs by value when you enumerate it:
var dict = new Dictionary<MyKey, MyValue>();
// insertion (updates value when key already exists)
dict[key] = value;
// enumeration (ordered by value)
foreach (var keyValuePair in dict.OrderBy(kvp => kvp.Value))
{
...
}
I would write an ad-hoc class like the following (not completely tested):
public class DictionarySortedByValue<TKey, TValue> : IDictionary<TKey, TValue>
{
class ValueWrapper : IComparable, IComparable<ValueWrapper>
{
public TKey Key { get; private set; }
public TValue Value { get; private set; }
public ValueWrapper(TKey k, TValue v)
{
this.Key = k;
this.Value = v;
}
public int CompareTo(object obj)
{
if (!(obj is ValueWrapper))
throw new ArgumentException("obj is not a ValueWrapper type object");
return this.CompareTo(obj as ValueWrapper);
}
public int CompareTo(ValueWrapper other)
{
int c = Comparer<TValue>.Default.Compare(this.Value, other.Value);
if (c == 0)
c = Comparer<TKey>.Default.Compare(this.Key, other.Key);
return c;
}
}
private SortedSet<ValueWrapper> orderedElements;
private SortedDictionary<TKey, TValue> innerDict;
public DictionarySortedByValue()
{
this.orderedElements = new SortedSet<ValueWrapper>();
this.innerDict = new SortedDictionary<TKey, TValue>();
}
public void Add(TKey key, TValue value)
{
var wrap = new ValueWrapper(key, value);
this.innerDict.Add(key, value);
this.orderedElements.Add(wrap);
}
public bool ContainsKey(TKey key)
{
return this.innerDict.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return this.innerDict.Keys; }
}
public bool Remove(TKey key)
{
TValue val;
if (this.TryGetValue(key, out val))
{
var wrap = new ValueWrapper(key, val);
this.orderedElements.Remove(wrap);
this.innerDict.Remove(key);
return true;
}
return false;
}
public bool TryGetValue(TKey key, out TValue value)
{
return this.innerDict.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return this.innerDict.Values; }
}
public TValue this[TKey key]
{
get
{
return this.innerDict[key];
}
set
{
bool removed = this.Remove(key);
this.Add(key, value);
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this.Add(item.Key, item.Value);
}
public void Clear()
{
this.innerDict.Clear();
this.orderedElements.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
var wrap = new ValueWrapper(item.Key,item.Value);
return this.orderedElements.Contains(wrap);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
this.innerDict.CopyTo(array, arrayIndex);
}
public int Count
{
get { return this.innerDict.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
if (this.Contains(item))
return this.Remove(item.Key);
return false;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (var el in this.orderedElements)
yield return new KeyValuePair<TKey, TValue>(el.Key, el.Value);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
Notes :
it requires that also the TKey type implements IComparable.
the posted code uses only the default Comparer for TKey, and TValue, but
you could pass a custom one through another constructor.
I am in need of a Generic collection that is somewhere in between a Dictionary and LinkedList. I want to be able to:
Access elements by key
Access previous and next elements
I've taken a look at the provided Generic collections as well as the specialized collections. I haven't really found what I'm looking for, the closest collections were OrderedDictionary and SortedDictionary.
A quick Google found the following potential collections:
LinkedDictionary - http://www.glennslayden.com/code/c-sharp/linked-dictionary
C5 collection (supports Hashed linked lists) - https://github.com/sestoft/C5/
Each seems like it could be a good fit. However, I wanted to ask the gurus at SO what their suggestions would be.
So gurus, what are your suggestions? Have you used these collections or other collections to accomplish these or related goals? Is there something blatantly obvious that I should be looking at and am just missing?
I think you found your own answer; C5 is good library and has what you are looking for, it has great documentation and tests. Oh, and it's available via Nuget.
I wrote my own. Comments & constructive criticism welcome.
/// <summary>
/// A LinkedDictionary is a hybrid of a LinkedList and a Dictionary.
/// The LinkedDictionary can be traversed in four ways:
/// 1. Enumerated using .GetEnumerator() - which doesn't guarantee key order
/// 2. Indexed using .ElementAt() - risky, as the underlying dictionary doesn't guarantee order integrity
/// 3. Looked up by key like a regular Dictionary - slow due to hashing and binary-tree search overhead https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs,bcd13bb775d408f1
/// 4. NEW WAY: crawled using .Next() or .Previous() - guarantees sequential order if values are added in key-sequential order
/// IT IS LINKED: Each node can be traversed back and forwards using Previous() and Next() like a LinkedList
/// IT IS OPTIONALLY SEQUENTIAL: if this flag is set, new values can only be added to at the end of the dictionary (keys are compared) to ensure index-sequence is preserved.
/// Partial inspiration from http://www.glennslayden.com/code/c-sharp/linked-dictionary
/// </summary>
public class LinkedDictionary<TKey, TValue>
: Dictionary<TKey, LinkedDictionaryEntry<TValue>>,
ILinkedDictionary<TKey, TValue>
where TKey : IComparable
{
#region Members
ICollection<TKey> IDictionary<TKey, TValue>.Keys => base.Keys;
ICollection<TValue> IDictionary<TKey, TValue>.Values => base.Values.Select(v => v.NodeValue).ToList();
public new TValue this[TKey index]
{
// kludge for serialization error "InvalidOperationException: You must implement a default accessor because SerializableDictionary inherits from ICollection"
// https://stackoverflow.com/questions/2331755/xmlserialize-exception
// TODO: check this works!
get
{
if (this.ContainsKey(index))
{
return base[index].NodeValue;
}
return default;
}
set => base[index] = new LinkedDictionaryEntry<TValue>(value);
}
public bool IsReadOnly => throw new NotImplementedException();
public bool IsSequential { get; }
#endregion Members
#region Constructor
public LinkedDictionary()
: base()
{
}
public LinkedDictionary(bool isSequential)
: base()
{
this.IsSequential = isSequential;
}
public LinkedDictionary(bool isSequential,
SerializationInfo info,
StreamingContext context)
: base(info, context)
{
this.IsSequential = isSequential;
}
public LinkedDictionary(bool isSequential, IEqualityComparer<TKey> comparer)
: base(comparer)
{
this.IsSequential = isSequential;
}
#endregion Constructor
#region Methods
#region Methods - Add
/// <summary>
/// Add new entry - check order if IsSequential flag is set
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public new void Add(TKey key, TValue value)
{
if (this.IsSequential)
{
// new item MUST have a key greater than the previous key; this is the optional Sequential character of this collection
if (this.Count > 0)
{
int comparisonResult = key.CompareTo(base.Keys.Last());
if (comparisonResult < 1)
throw new LoggedException("Cannot add new item. Key must be greater than the last key.");
}
}
// Create the new Linked entry
LinkedDictionaryEntry<TValue> newEntry = new LinkedDictionaryEntry<TValue>(value);
// Link the new value in IF there's something to link it to
if (base.Values.Count > 0)
{
LinkedDictionaryEntry<TValue> existingLastValue = base.Values.Last();
// Create forwards-looking link from the existing last entry to the new entry
existingLastValue.Next = newEntry;
// Create backwards-looking link from new entry to the existing last entry
newEntry.Previous = existingLastValue;
}
base.Add(key, newEntry);
;
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this.Add(item.Key, item.Value);
}
#endregion Methods - Add
#region Methods - Get
public bool TryGetValue(TKey key, out TValue value)
{
if (base.TryGetValue(key, out var theNode))
{
value = theNode.NodeValue;
return true;
}
value = default(TValue);
return false;
}
public TKey FirstKey() => this.Keys.First();
public TKey LastKey() => this.Keys.Last();
public ILinkedDictionaryEntry<TValue> FirstNode() => this.Values.First();
public ILinkedDictionaryEntry<TValue> LastNode() => this.Values.Last();
public TValue FirstValue() => this.Values.First().NodeValue;
public TValue LastValue() => this.Values.Last().NodeValue;
#endregion Methods - Get
#region Methods - Remove
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return base.Remove(item.Key);
}
#endregion Methods - Remove
public bool Contains(KeyValuePair<TKey, TValue> item)
{
throw new NotImplementedException();
}
public new IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return base.GetEnumerator() as IEnumerator<KeyValuePair<TKey, TValue>>;
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
#endregion Methods
};
/// <summary>
/// LinkedDictionaryEntry
/// Base type for Values in LinkedDictionary<K, V>
/// </summary>
[DebuggerDisplay("_previous = {Previous} _next = {Next}")]
public class LinkedDictionaryEntry<TValue>
: ILinkedDictionaryEntry<TValue>
{
#region Members
#region Members - Next
public ILinkedDictionaryEntry<TValue> Next { get; set; }
public ILinkedDictionaryEntry NextUntyped => this.Next;
#endregion Members - Next
#region Members - Previous
public ILinkedDictionaryEntry<TValue> Previous { get; set; }
public ILinkedDictionaryEntry PreviousUntyped => this.Previous;
#endregion Members - Previous
// VALUE
public TValue NodeValue { get; }
#endregion Members
#region Constructor
public LinkedDictionaryEntry(TValue nodeValue)
{
this.NodeValue = nodeValue;
}
#endregion Constructor
#region Methods
// Remove this item from a list by patching over it
public void Unlink()
{
this.Previous.Next = this.Next;
this.Next.Previous = this.Previous;
this.Next = this.Previous = null;
}
// Insert this item into a list after the specified element
public void InsertAfter(LinkedDictionaryEntry<TValue> e)
{
e.Next.Previous = this;
this.Next = e.Next;
this.Previous = e;
e.Next = this;
}
public void SetNext<TValue>(ILinkedDictionaryEntry<TValue> newEntry)
{
throw new NotImplementedException();
}
public override string ToString()
{
return this.NodeValue.ToString();
}
#endregion Methods
};
public interface ILinkedDictionary<TKey, TValue>
: IDictionary<TKey, TValue>
{
TKey LastKey();
TKey FirstKey();
TValue FirstValue();
TValue LastValue();
new void Add(TKey key, TValue value);
string ToString();
}
public interface ILinkedDictionaryEntry
{
ILinkedDictionaryEntry PreviousUntyped { get; }
ILinkedDictionaryEntry NextUntyped { get; }
}
public interface ILinkedDictionaryEntry<TValue>
: ILinkedDictionaryEntry
{
ILinkedDictionaryEntry<TValue> Previous { get; set; }
ILinkedDictionaryEntry<TValue> Next { get; set; }
TValue NodeValue { get; }
}
I thought of solution below because the collection is very very small. But what if it was big?
private Dictionary<string, OfTable> _folderData = new Dictionary<string, OfTable>();
public Dictionary<string, OfTable> FolderData
{
get { return new Dictionary<string,OfTable>(_folderData); }
}
With List you can make:
public class MyClass
{
private List<int> _items = new List<int>();
public IList<int> Items
{
get { return _items.AsReadOnly(); }
}
}
That would be nice!
Thanks in advance, Cheers & BR - Matti
NOW WHEN I THINK THE OBJECTS IN COLLECTION ARE IN HEAP. SO MY SOLUTION DOES NOT PREVENT THE CALLER TO MODIFY THEM!!! CAUSE BOTH Dictionary s CONTAIN REFERENCES TO SAME OBJECT. DOES THIS APPLY TO List EXAMPLE ABOVE?
class OfTable
{
private int _table;
private List<int> _classes;
private string _label;
public OfTable()
{
_classes = new List<int>();
}
public int Table
{
get { return _table; }
set { _table = value; }
}
public List<int> Classes
{
get { return _classes; }
set { _classes = value; }
}
public string Label
{
get { return _label; }
set { _label = value; }
}
}
so how to make this immutable??
It's not difficult to roll your own ReadOnlyDictionary<K,V> wrapper class. Something like this:
public sealed class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private readonly IDictionary<TKey, TValue> _dictionary;
public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
public bool ContainsKey(TKey key)
{
return _dictionary.ContainsKey(key);
}
public int Count
{
get { return _dictionary.Count; }
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
public ICollection<TKey> Keys
{
get { return _dictionary.Keys; }
}
public bool TryGetValue(TKey key, out TValue value)
{
return _dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return _dictionary.Values; }
}
public TValue this[TKey key] // Item
{
get { return _dictionary[key]; }
}
#region IDictionary<TKey, TValue> Explicit Interface Implementation
void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
{
throw new NotSupportedException("Dictionary is read-only.");
}
bool IDictionary<TKey, TValue>.Remove(TKey key)
{
throw new NotSupportedException("Dictionary is read-only.");
}
TValue IDictionary<TKey, TValue>.this[TKey key] // Item
{
get { return _dictionary[key]; }
set { throw new NotSupportedException("Dictionary is read-only."); }
}
#endregion
#region ICollection<T> Explicit Interface Implementation
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
throw new NotSupportedException("Collection is read-only.");
}
void ICollection<KeyValuePair<TKey, TValue>>.Clear()
{
throw new NotSupportedException("Collection is read-only.");
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
return _dictionary.Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_dictionary.CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
{
get { return true; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
throw new NotSupportedException("Collection is read-only.");
}
#endregion
#region IEnumerable Explicit Interface Implementation
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_dictionary).GetEnumerator();
}
#endregion
}
If you're using C#3 or later then you could knock-up a matching AsReadOnly extension method too:
public static class ReadOnlyDictionaryHelper
{
public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
{
var temp = dictionary as ReadOnlyDictionary<TKey, TValue>;
return temp ?? new ReadOnlyDictionary<TKey, TValue>(dictionary);
}
}
And then return the read-only wrapper from your property:
// in C#2
return new ReadOnlyDictionary<string, OfTable>(_folderData);
// in C#3 or later
return _folderData.AsReadOnly();
Use ReadOnlyCollection<T> class.
An instance of the ReadOnlyCollection generic class is always read-only. A collection that is read-only is simply a collection with a wrapper that prevents modifying the collection; therefore, if changes are made to the underlying collection, the read-only collection reflects those changes. See Collection for a modifiable version of this class.
--EDIT--
Checkout a trivial dictionary wrapper here. And A Generic Read-Only Dictionary by Richard Carr.