Is there a collection in C# that will not let you add duplicate items to it? For example, with the silly class of
public class Customer {
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public override int GetHashCode() {
return (FirstName + LastName + Address).GetHashCode();
}
public override bool Equals(object obj) {
Customer C = obj as Customer;
return C != null && String.Equals(this.FirstName, C.FirstName) && String.Equals(this.LastName, C.LastName) && String.Equals(this.Address, C.Address);
}
}
The following code will (obviously) throw an exception:
Customer Adam = new Customer { Address = "A", FirstName = "Adam", LastName = "" };
Customer AdamDup = new Customer { Address = "A", FirstName = "Adam", LastName = "" };
Dictionary<Customer, bool> CustomerHash = new Dictionary<Customer, bool>();
CustomerHash.Add(Adam, true);
CustomerHash.Add(AdamDup, true);
But is there a class that will similarly guarantee uniqueness, but without KeyValuePairs? I thought HashSet<T> would do that, but having read the docs it seems that class is just a set implementation (go figure).
HashSet<T> is what you're looking for. From MSDN (emphasis added):
The HashSet<T> class provides high-performance set operations. A set is a collection that contains no duplicate elements, and whose elements are in no particular order.
Note that the HashSet<T>.Add(T item) method returns a bool -- true if the item was added to the collection; false if the item was already present.
How about just an extension method on HashSet?
public static void AddOrThrow<T>(this HashSet<T> hash, T item)
{
if (!hash.Add(item))
throw new ValueExistingException();
}
From the HashSet<T> page on MSDN:
The HashSet(Of T) class provides high-performance set operations. A set is a collection that contains no duplicate elements, and whose elements are in no particular order.
(emphasis mine)
If all you need is to ensure uniqueness of elements, then HashSet is what you need.
What do you mean when you say "just a set implementation"? A set is (by definition) a collection of unique elements that doesn't save element order.
Just to add my 2 cents...
if you need a ValueExistingException-throwing HashSet<T> you can also create your collection easily:
public class ThrowingHashSet<T> : ICollection<T>
{
private HashSet<T> innerHash = new HashSet<T>();
public void Add(T item)
{
if (!innerHash.Add(item))
throw new ValueExistingException();
}
public void Clear()
{
innerHash.Clear();
}
public bool Contains(T item)
{
return innerHash.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
innerHash.CopyTo(array, arrayIndex);
}
public int Count
{
get { return innerHash.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
return innerHash.Remove(item);
}
public IEnumerator<T> GetEnumerator()
{
return innerHash.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
this can be useful for example if you need it in many places...
You can try HashSet<T>
You may look into something kind of Unique List as follows
public class UniqueList<T>
{
public List<T> List
{
get;
private set;
}
List<T> _internalList;
public static UniqueList<T> NewList
{
get
{
return new UniqueList<T>();
}
}
private UniqueList()
{
_internalList = new List<T>();
List = new List<T>();
}
public void Add(T value)
{
List.Clear();
_internalList.Add(value);
List.AddRange(_internalList.Distinct());
//return List;
}
public void Add(params T[] values)
{
List.Clear();
_internalList.AddRange(values);
List.AddRange(_internalList.Distinct());
// return List;
}
public bool Has(T value)
{
return List.Contains(value);
}
}
and you can use it like follows
var uniquelist = UniqueList<string>.NewList;
uniquelist.Add("abc","def","ghi","jkl","mno");
uniquelist.Add("abc","jkl");
var _myList = uniquelist.List;
will only return "abc","def","ghi","jkl","mno" always even when duplicates are added to it
As an overall check different methods here are 4 ways to check if the collection has not any duplicates:
public static bool LinqAny<T>(IEnumerable<T> enumerable)
{
HashSet<T> set = new();
return enumerable.Any(element => !set.Add(element));
}
public static bool LinqAll<T>(IEnumerable<T> enumerable)
{
HashSet<T> set = new();
return !enumerable.All(set.Add);
}
public static bool LinqDistinct<T>(IEnumerable<T> enumerable)
{
return enumerable.Distinct().Count() != enumerable.Count();
}
public static bool ToHashSet<T>(IEnumerable<T> enumerable)
{
return enumerable.ToHashSet().Count != enumerable.Count();
}
Related
This question already has answers here:
How to serialize/deserialize a custom collection with additional properties using Json.Net
(6 answers)
Closed 7 years ago.
I created a custom List class that maintains a set of item ids for performance reasons:
public class MyCustomList : List<ItemWithID>
{
private HashSet<int> itemIDs = new HashSet<int>();
public MyCustomList()
{
}
[JsonConstructor]
public MyCustomList(IEnumerable<ItemWithID> collection)
: base(collection)
{
itemIDs = new HashSet<int>(this.Select(i => i.ID));
}
public new void Add(ItemWithID item)
{
base.Add(item);
itemIDs.Add(item.ID);
}
public new bool Remove(ItemWithID item)
{
var removed = base.Remove(item);
if (removed)
{
itemIDs.Remove(item.ID);
}
return removed;
}
public bool ContainsID(int id)
{
return itemIDs.Contains(id);
}
}
I want to deserialize this List from a simply JSON array e.g.:
JsonConvert.DeserializeObject<MyCustomList>("[{ID:8},{ID:9}]");
this will cause JSON.NET to call only the empty constructor, so my itemIDs list remains empty. Also the Add method is not called.
How does JSON.NET add the items to the list so I can add logic at that place.
(this is about deserialization without properties that should be persistent in the json string, so the suggested duplicate question has nothing to do with this one)
Solution:
public class MyCustomList : IList<ItemWithID>
{
private HashSet<int> itemIDs = new HashSet<int>();
private List<ItemWithID> actualList = new List<ItemWithID>();
public void Add(ItemWithID item)
{
actualList.Add(item);
itemIDs.Add(item.ID);
}
public bool Remove(ItemWithID item)
{
var removed = actualList.Remove(item);
if (removed)
{
itemIDs.Remove(item.ID);
}
return removed;
}
public bool ContainsID(int id)
{
return itemIDs.Contains(id);
}
public int IndexOf(ItemWithID item)
{
return actualList.IndexOf(item);
}
public void Insert(int index, ItemWithID item)
{
actualList.Insert(index, item);
itemIDs.Add(item.ID);
}
public void RemoveAt(int index)
{
itemIDs.Remove(actualList[index].ID);
actualList.RemoveAt(index);
}
public ItemWithID this[int index]
{
get
{
return actualList[index];
}
set
{
actualList[index] = value;
if (!itemIDs.Contains(value.ID))
{
itemIDs.Add(value.ID);
}
}
}
public void Clear()
{
actualList.Clear();
itemIDs.Clear();
}
public bool Contains(ItemWithID item)
{
return actualList.Contains(item);
}
public void CopyTo(ItemWithID[] array, int arrayIndex)
{
actualList.CopyTo(array, arrayIndex);
}
public int Count
{
get { return actualList.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public IEnumerator<ItemWithID> GetEnumerator()
{
return actualList.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
You could deserialize to the form the constructor expects, then call that yourself.
var collection = JsonConvert.DeserializeObject<ItemID[]>("[{ID:8},{ID:9}]");
var aCustomList = new MyCustomList(collection);
Your problem isn't with JSON deserialization, your MyCustomList class needs to derive from IList if you want to be able to override the Add method. See THIS for details.
I am looking for a collection where no element can exist more than once, and are also indexed. Similar to Dictionary, but without Key, just Value. Similar to a HashSet, but indexed so I can easily retrieve an element without iterating over the collection. I hope this makes sense. :)
You can use a HashSet. It is "indexed", after all, performance would be lacking if it weren't.
Use the Contains method to "retrieve" an element. If you want to remove it as well, use Remove.
Both methods are O(1) operations.
You can use a Dictionary<T, T> for that and insert elements using Add(value, value).
However, that only makes sense if your type properly implements Equals(object) and GetHashCode(). If it doesn't, two different instanced will never be equal and the HashSet<T>'s Contains(T) method already tells you whether you have the element reference of nor.
HashSet class is best for your work. I won't allow duplicate entries.
Note that the HashSet.Add(T item) method returns a bool -- true if the item was added to the collection; false if the item was already present.
Simply you can add an Extension method to throw exception as
public static void AddOrThrow<T>(this HashSet<T> hash, T item)
{
if (!hash.Add(item))
throw new ValueExistingException();
}
The easiest way to do this is make a class that implements IList<T> but uses a List<T> and HashSet<T> internally. You then just have each method act on each collection as needed.
using System;
using System.Collections.Generic;
namespace Example
{
public class UniqueList<T> : IList<T>
{
private readonly List<T> _list;
private readonly HashSet<T> _hashset;
public UniqueList()
{
_list = new List<T>();
_hashset = new HashSet<T>();
}
public UniqueList(IEqualityComparer<T> comparer)
{
_list = new List<T>();
_hashset = new HashSet<T>(comparer);
}
void ICollection<T>.Add(T item)
{
Add(item);
}
public bool Add(T item)
{
var added = _hashset.Add(item);
if (added)
{
_list.Add(item);
}
return added;
}
public void RemoveAt(int index)
{
_hashset.Remove(_list[index]);
_list.RemoveAt(index);
}
public T this[int index]
{
get { return _list[index]; }
set
{
var oldItem = _list[index];
_hashset.Remove(oldItem);
var added = _hashset.Add(value);
if (added)
{
_list[index] = value;
}
else
{
//Put the old item back before we raise a exception.
_hashset.Add(oldItem);
throw new InvalidOperationException("Object already exists.");
}
}
}
public int IndexOf(T item)
{
return _list.IndexOf(item);
}
void IList<T>.Insert(int index, T item)
{
Insert(index, item);
}
public bool Insert(int index, T item)
{
var added = _hashset.Add(item);
if (added)
{
_list.Insert(index, item);
}
return added;
}
public void Clear()
{
_list.Clear();
_hashset.Clear();
}
public bool Contains(T item)
{
return _hashset.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
var removed = _hashset.Remove(item);
if (removed)
{
_list.Remove(item);
}
return removed;
}
public int Count
{
get { return _list.Count; }
}
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
I did explicit implementations of Add and Insert so I could give them versions that returned a bool to tell if the operation succeeded or not. I could not return a value in the T this[int index] setter so I have it throw a InvalidOperationException if you attempt to insert a duplicate.
It does not throw if you do ICollection.Add on a duplicate, it just does not add it. This is because that is the behavior HashSet<T>.ICollection<T>.Add has and i wanted to mimic it.
I have a class 'products' that is serializable to XML. I'm using the standard System.Xml.Serialization.XmlSerializer to serialize and a XmlWriter 'writer' object to write the serialized results to a StreamWriter object. The serializer object now serializes the whole class in one go:
XmlSerializer serializer = new XmlSerializer(typeof(products));
serializer.Serialize(writer, products);
The class has a Dictionary<string,string> member called 'Specifications'. It is dynamically built, so I don't know the keys beforehand. Here's an example of what data the dictionary may contain (key: value):
color: blue
length: 110mm
width: 55mm
I would like to be able to serialize that property into this:
...
<specifications>
<color>blue</color>
<length>110mm</length>
<width>55mm</width>
</specifications>
...
I know this is poor XML design, but it has to conform to a 3rd party specification.
Is there perhaps a standard attribute that I can use? If not, how would I be able to serialize the dictionary like that?
If you need more code snippets, let me know.
EDIT:
Due to some changes in requirement, I let go of the Dictionary<string,string>. Instead, I created a class "Specification":
public class Specification
{
public string Name;
public string Value;
public bool IsOther;
public Specification() : this(null, null, false) { }
public Specification(string name, string value) : this(name, value, false) { }
public Specification(string name, string value, bool isOther)
{
Name = name;
Value = value;
IsOther = isOther;
}
}
To avoid repeating the element "spec" by having a List of "Specification" in the product class, I use a plural class "Specifications" that implements the IXmlSerializable interface:
public class Specifications: IXmlSerializable
{
public List<Specification> Specs = new List<Specification>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
//I don't need deserialization, but it would be simple enough now.
throw new System.NotImplementedException();
}
public void WriteXml(XmlWriter writer)
{
//write all "standarad", named specs
//this writes the <color>blue</color>-like elements
Specs.Where(s => !s.IsOther).ToList().ForEach(s => writer.WriteElementString(s.Name, s.Value));
//write other specs
//this writes <other_specs>{name|value[;]}*</other_specs>
string otherSpecs = string.Join(";", Specs.Where(s => s.IsOther).Select(s => string.Concat(s.Name, "|", s.Value)));
if (otherSpecs.Length > 0) writer.WriteElementString("other_specs", otherSpecs);
}
}
The class "Specifications" is applied as:
public class Product
{
public Product()
{
Specifications = new Specifications();
}
[XmlElement("specs")]
public Specifications Specifications;
//this "feature" will not include <specs/> when there are none
[XmlIgnore]
public bool SpecificationsSpecified { get { return Specifications.Specs.Any(); } }
//...
}
Thank you for providing examples of IXmlSerializable and XmlWriter. I didn't know that interface and usage of XmlWriter - it proved to be a valuable inspiration for me!
*this was my first SO question. What's the most appropriate way to close it? I didn't provide this as my own answer as it is not a real answer to my initial question (about Dictionary).
Assuming that your dictionary value are all simple types that can be converted to a string, you can create your own IXmlSerializable dictionary wrapper to store and retrieve the keys and values:
public class XmlKeyTextValueListWrapper<TValue> : CollectionWrapper<KeyValuePair<string, TValue>>, IXmlSerializable
{
public XmlKeyTextValueListWrapper() : base(new List<KeyValuePair<string, TValue>>()) { } // For deserialization.
public XmlKeyTextValueListWrapper(ICollection<KeyValuePair<string, TValue>> baseCollection) : base(baseCollection) { }
public XmlKeyTextValueListWrapper(Func<ICollection<KeyValuePair<string, TValue>>> getCollection) : base(getCollection) {}
#region IXmlSerializable Members
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.ReadXml(reader, this, converter);
}
public void WriteXml(XmlWriter writer)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.WriteXml(writer, this, converter);
}
#endregion
}
public static class XmlKeyValueListHelper
{
public static void WriteXml<T>(XmlWriter writer, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
{
foreach (var pair in collection)
{
writer.WriteStartElement(XmlConvert.EncodeName(pair.Key));
writer.WriteValue(typeConverter.ConvertToInvariantString(pair.Value));
writer.WriteEndElement();
}
}
public static void ReadXml<T>(XmlReader reader, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var key = XmlConvert.DecodeName(reader.Name);
string value;
if (reader.IsEmptyElement)
{
value = string.Empty;
// Move past the end of item element
reader.Read();
}
else
{
// Read content and move past the end of item element
value = reader.ReadElementContentAsString();
}
collection.Add(new KeyValuePair<string,T>(key, (T)typeConverter.ConvertFromInvariantString(value)));
}
// Move past the end of the list element
reader.ReadEndElement();
}
public static void CopyTo<TValue>(this XmlKeyTextValueListWrapper<TValue> collection, ICollection<KeyValuePair<string, TValue>> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
if (collection == null)
dictionary.Clear();
else
{
if (collection.IsWrapperFor(dictionary)) // For efficiency
return;
var pairs = collection.ToList();
dictionary.Clear();
foreach (var item in pairs)
dictionary.Add(item);
}
}
}
public class CollectionWrapper<T> : ICollection<T>
{
readonly Func<ICollection<T>> getCollection;
public CollectionWrapper(ICollection<T> baseCollection)
{
if (baseCollection == null)
throw new ArgumentNullException();
this.getCollection = () => baseCollection;
}
public CollectionWrapper(Func<ICollection<T>> getCollection)
{
if (getCollection == null)
throw new ArgumentNullException();
this.getCollection = getCollection;
}
public bool IsWrapperFor(ICollection<T> other)
{
if (other == Collection)
return true;
var otherWrapper = other as CollectionWrapper<T>;
return otherWrapper != null && otherWrapper.IsWrapperFor(Collection);
}
ICollection<T> Collection { get { return getCollection(); } }
#region ICollection<T> Members
public void Add(T item)
{
Collection.Add(item);
}
public void Clear()
{
Collection.Clear();
}
public bool Contains(T item)
{
return Collection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
Collection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Collection.Count; }
}
public bool IsReadOnly
{
get { return Collection.IsReadOnly; }
}
public bool Remove(T item)
{
return Collection.Remove(item);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return Collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
And then use it like so:
[XmlRoot("products")]
public class Products
{
public Products()
{
Specifications = new Dictionary<string, string>();
}
[XmlIgnore]
[JsonProperty("specifications")] // For testing purposes, I compare Json.NET serialization before and after XML serialization. You can remove this.
public Dictionary<string, string> Specifications { get; set; }
[XmlElement("specifications")]
[JsonIgnore] // For testing purposes, I compare Json.NET serialization before and after XML serialization. You can remove this.
public XmlKeyTextValueListWrapper<string> XmlSpecifications
{
get
{
return new XmlKeyTextValueListWrapper<string>(() => this.Specifications);
}
set
{
value.CopyTo(Specifications = (Specifications ?? new Dictionary<string, string>()));
}
}
}
The fact that your dictionary values are simple types (directly convertible from and to text) makes it possible to avoid nested creations of XmlSerializer, which is more complex. See here for an example.
Make the dictionary NonSerialized
[XmlRoot("specifications")]
public class Specifications
{
[NonSerialized]
Dictionary<string, string> dict { get; set; }
[XmlElement("color")]
string color {get;set;}
[XmlElement("length")]
string length { get; set; }
[XmlElement("width")]
string width { get; set; }
public Specifications()
{
dict = new Dictionary<string, string>();
}
}
What is the most efficient (in terms of speed) implementation of UniqueQueue and UniqueReplacementQueue collections in .NET considering the fact that the speed of Enqueue and Dequeue operations is equally important.
UniqueQueue is a queue where duplicates are not possible. So if I push an element to the queue it is added in only case it doesn't already exist in the queue.
UniqueReplacementQueue is a queue where duplicates are not possible either. The difference is that if I push an element which already exists in the queue, it replaces the existing element at the same position. It makes sense for reference types.
My current implementation of UniqueQueue and UniqueReplacementQueue:
sealed class UniqueQueue<T> : IQueue<T>
{
readonly LinkedList<T> list;
readonly IDictionary<T, int> dictionary;
public UniqueQueue(LinkedList<T> list, IDictionary<T, int> dictionary)
{
this.list = list;
this.dictionary = dictionary;
}
public int Length
{
get { return list.Count; }
}
public T Dequeue()
{
if (list.Count == 0)
{
throw new InvalidOperationException("The queue is empty");
}
var element = list.First.Value;
dictionary.Remove(element);
list.RemoveFirst();
return element;
}
public void Enqueue(T element)
{
dictionary[element] = 0;
if (dictionary.Count > list.Count)
{
list.AddLast(element);
}
}
}
sealed class UniqueReplacementQueue<T> : IQueue<T>
{
readonly LinkedList<T> list;
readonly IDictionary<T, T> dictionary;
public UniqueReplacementQueue(LinkedList<T> list, IDictionary<T, T> dictionary)
{
this.list = list;
this.dictionary = dictionary;
}
public int Length
{
get { return list.Count; }
}
public T Dequeue()
{
if (list.Count == 0)
{
throw new InvalidOperationException("The queue is empty");
}
var element = dictionary[list.First.Value];
dictionary.Remove(element);
list.RemoveFirst();
return element;
}
public void Enqueue(T element)
{
dictionary[element] = element;
if (dictionary.Count > list.Count)
{
list.AddLast(element);
}
}
}
This is pretty old, but how about a class that has an internal HashSet, and Queue. A custom method for Enqueue firsts tries to add it to the hashset. if the HashSet.Add call returns false, we do not enqueue it. HashSet.Add() is an O(1) operation if the set is of a size large enough to hold all elements.
The only drawback to this is memory usage if this is a concern for you. Here is an implementation:
public class UniqueQueue<T> : IEnumerable<T> {
private HashSet<T> hashSet;
private Queue<T> queue;
public UniqueQueue() {
hashSet = new HashSet<T>();
queue = new Queue<T>();
}
public int Count {
get {
return hashSet.Count;
}
}
public void Clear() {
hashSet.Clear();
queue.Clear();
}
public bool Contains(T item) {
return hashSet.Contains(item);
}
public void Enqueue(T item) {
if (hashSet.Add(item)) {
queue.Enqueue(item);
}
}
public T Dequeue() {
T item = queue.Dequeue();
hashSet.Remove(item);
return item;
}
public T Peek() {
return queue.Peek();
}
public IEnumerator<T> GetEnumerator() {
return queue.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return queue.GetEnumerator();
}
}
The HashSet is used whenever it can because it is typically faster. This could be nicer if the maintainers of .NET marked these methods as virtual, but alas here we are.
How about this?
//the UniqueQueueItem has the key in itself,
//and implements the IUniqueQueueItemable to copy the other values.
//For example:
class TestUniqueQueueItem : IUniqueQueueItemable<TestUniqueQueueItem>
{
//Key
public int Id { get; set; }
public string Name { get; set; }
public override int GetHashCode()
{
return Id;
}
//To copy the other values.
public void CopyWith(TestUniqueQueueItem item)
{
this.Name = item.Name;
}
public override bool Equals(object obj)
{
return this.Id == ((TestUniqueQueueItem)obj).Id;
}
}
internal interface IUniqueQueueItemable<in T>
{
void CopyWith(T item);
}
class UniqueQueue<T> where T: IUniqueQueueItemable<T>
{
private readonly bool _isReplacementQueue;
private readonly Queue<T> _queue;
private readonly Dictionary<T, T> _dictionary;
public UniqueQueue(): this(false)
{
}
public UniqueQueue(bool isReplacementQueue)
{
_isReplacementQueue = isReplacementQueue;
_queue = new Queue<T>();
_dictionary = new Dictionary<T, T>();
}
public void Enqueue(T item)
{
if(!_dictionary.Keys.Contains(item))
{
_dictionary.Add(item, item);
_queue.Enqueue(item);
}
else
{
if(_isReplacementQueue)
{
//it will return the existedItem, which is the same key with the item
//but has different values with it.
var existedItem = _dictionary[item];
//copy the item to the existedItem.
existedItem.CopyWith(item);
}
}
}
public T Dequeue()
{
var item = _queue.Dequeue();
_dictionary.Remove(item);
return item;
}
}
I am serializing Lists of classes which are my data entities. I have a DataProvider that contains a List.
I always modify items directly within the collection.
What is the best way of determining if any items in the List have changed? I am using the Compact Framework.
My only current idea is to create a hash of the List (if that's possible) when I load the list. Then when I do a save I re-get the hash of the list and see if they're different values. If they're different I save and then update the stored Hash for comparison later, if they're the same then I don't save.
Any ideas?
If the items you add to the list implement the INotifyPropertyChanged interface, you could build your own generic list that hooks the event in that interface for all objects you add to the list, and unhooks the event when the items are removed from the list.
There's a BindingList<T> class in the framework you can use, or you can write your own.
Here's a sample add method, assuming the type has been declared with where T: INotifyPropertyChanged:
public void Add(T item)
{
// null-check omitted for simplicity
item.PropertyChanged += ItemPropertyChanged;
_List.Add(item);
}
and the this[index] indexer property:
public T this[Int32 index]
{
get { return _List[index]; }
set {
T oldItem = _List[index];
_List[index] = value;
if (oldItem != value)
{
if (oldItem != null)
oldItem.PropertyChanged -= ItemPropertyChanged;
if (value != null)
value.PropertyChanged += ItemPropertyChanged;
}
}
}
If your items doesn't support INotifyPropertyChanged, but they're your classes, I would consider adding that support.
You could create your own IList<T> class, say DirtyList<T> that can record when the list has changed.
If you're willing to use reflection, the List<T> class has a private field called _version that is incremented every time the list changes. It won't tell you which items have changed, but you can compare it with the original value of _version to detect an unmodified list.
For reference, this field is used to ensure that enumerators become invalid when the list is modified. So you should be able to use it for your purposes fairly reliably, unless the actual managed code for List<T> changes.
To get the value of _version you can use something like this:
List<T> myList;
var field = myList.GetType().GetField("_version", BindingFlags.Instance | BindingFlags.NonPublic);
int version = field.GetValue(myList);
Generally speaking, though, this isn't the best approach. If you're stuck using a List<T> that someone else created, however, it's probably the best option you have. Please be aware that changes to the .NET framework could change the name of the field (or remove it entirely), and it's not guaranteed to exist in third-party CLR implementations like Mono.
How about something like this?
public class ItemChangedArgs<T> : EventArgs
{
public int Index { get; set; }
public T Item { get; set; }
}
public class EventList<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable
{
private List<T> m_list;
public event EventHandler<ItemChangedArgs<T>> ItemAdded;
public event EventHandler<ItemChangedArgs<T>> ItemRemoved;
public event EventHandler<ItemChangedArgs<T>> ItemChanged;
public event EventHandler ListCleared;
public EventList(IEnumerable<T> collection)
{
m_list = new List<T>(collection);
}
public EventList(int capacity)
{
m_list = new List<T>(capacity);
}
public EventList()
{
m_list = new List<T>();
}
public void Add(T item)
{
Add(item, true);
}
public void Add(T item, Boolean raiseEvent)
{
m_list.Add(item);
if (raiseEvent) RaiseItemAdded(this.Count - 1, item);
}
public void AddRange(IEnumerable<T> collection)
{
foreach (T t in collection)
{
m_list.Add(t);
}
}
private void RaiseItemAdded(int index, T item)
{
if (ItemAdded == null) return;
ItemAdded(this, new ItemChangedArgs<T> { Index = index, Item = item });
}
public int IndexOf(T item)
{
return m_list.IndexOf(item);
}
public void Insert(int index, T item)
{
m_list.Insert(index, item);
RaiseItemAdded(index, item);
}
public void RemoveAt(int index)
{
T item = m_list[index];
m_list.RemoveAt(index);
RaiseItemRemoved(index, item);
}
private void RaiseItemRemoved(int index, T item)
{
if(ItemRemoved == null) return;
ItemRemoved(this, new ItemChangedArgs<T> { Index = index, Item = item });
}
public T this[int index]
{
get { return m_list[index]; }
set
{
m_list[index] = value;
RaiseItemChanged(index, m_list[index]);
}
}
private void RaiseItemChanged(int index, T item)
{
if(ItemChanged == null) return;
ItemChanged(this, new ItemChangedArgs<T> { Index = index, Item = item });
}
public void Clear()
{
m_list.Clear();
RaiseListCleared();
}
private void RaiseListCleared()
{
if(ListCleared == null) return;
ListCleared(this, null);
}
public bool Contains(T item)
{
return m_list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
m_list.CopyTo(array, arrayIndex);
}
public int Count
{
get { return m_list.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
for (int i = 0; i < m_list.Count; i++)
{
if(item.Equals(m_list[i]))
{
T value = m_list[i];
m_list.RemoveAt(i);
RaiseItemRemoved(i, value);
return true;
}
}
return false;
}
public IEnumerator<T> GetEnumerator()
{
return m_list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_list.GetEnumerator();
}
}
Assuming that GetHashCode() for every member contained in the list is implemented properly (and thus changes when an element changes) I'd imagine something along the lines of:
public class DirtyList<T> : List<T> {
private IList<int> hashCodes = new List<int> hashCodes();
public DirtyList() : base() { }
public DirtyList(IEnumerable<T> items) : base() {
foreach(T item in items){
this.Add(item); //Add it to the collection
hashCodes.Add(item.GetHashCode());
}
}
public override void Add(T item){
base.Add(item);
hashCodes.Add(item);
}
//Add more logic for the setter and also handle the case where items are removed and indexes change and etc, also what happens in case of null values?
public bool IsDirty {
get {
for(int i = 0; i < Count: i++){
if(hashCodes[i] != this[i].GetHashCode()){ return true; }
}
return false;
}
}
}
*Please be aware i typed this up on SO and do not have a compiler, so above stated code is in no way guarenteed to work, but hopefully it'll show the idea.
You could implement you're own list that maintains 2 internal lists... and instantiated version and tracking version... e.g.
//Rough Psuedo Code
public class TrackedList<T> : List<T>
{
public bool StartTracking {get; set; }
private List<T> InitialList { get; set; }
CTOR
{
//Instantiate Both Lists...
}
ADD(item)
{
if(!StartTracking)
{
Base.Add(item);
InitialList.Add(item);
}
else
{
Base.Add(item);
}
}
public bool IsDirty
{
get
{
Check if theres any differences between initial list and self.
}
}
}
Make sure that T is a descendant of an object that has a dirty flag and have the IList implementation have a check for that which walks the list's dirty flags.