I have two Lists that I'm trying to compare. What I need to accomplish is essentially to remove the items that are duplicated between the two lists and only keep the objects that are different. Right now, I'm inserting the non-duplicate data into a new list.
The data I'm using here...
LIST1
("b",2)
("c",3)
LIST2
("a",1)
("b",2)
("c",3)
("d",4)
NEWLIST
("a",1)
("d",4)
Here's what I have so far...
My object:
public class TestClass
{
protected string teststring;
protected int testint;
public string TestString
{
get { return teststring; }
set { teststring = value; }
}
public int TestInt
{
get { return testint; }
set { testint = value; }
}
public TestClass() { }
}
My compare logic:
private static List<TestClass> CompareCodes(List<TestClass> list1, List<TestClass> list2)
{
List<TestClass> newList = new List<TestClass>();
foreach (TestClass s in list2)
{
if (list1.Contains(s) == false)
{
newList.Add(s);
}
}
if (newList.Count != 0)
return newList;
else
return null;
}
The new list will be used to insert data into a database table. If it's null, no action will be taken. I'm using .NET 2.0 in this app (it's an enhancement to an older app), so I can't use LINQ. So is there any other way to make this work that I'm missing? Or is there a better way to do this? I haven't been able to find anything (maybe just not looking hard enough) to accomplish what I'm trying to do.
Thanks in advance!
So you're almost there, but you'll need to override the Equals method on your class to make it work:
public class TestClass
{
public override bool Equals(object y)
{
TestClass newY = y as TestClass;
if (newY == null) { return false; }
return newY.TestString == this.TestString &&
newY.TestInt == this.TestInt;
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + this.TestInt.GetHashCode();
hash = hash * 23 + this.TestString == null ?
0 :
this.TestString.GetHashCode();
return hash;
}
}
}
Use Jon Skeet's answer here to implement your hash code.
In the code that you have provided you are only keeping the items of list2 that are not in list1. but how about the items that are in list1 but not in list2 ? and since you mention
What I need to accomplish is essentially to remove the items that are duplicated between the two lists and only keep the objects that are different
This code belows returns a new list with the items that are unique in both lists
private static List<TestClass> CompareCodes(List<TestClass> list1, List<TestClass> list2)
{
List<TestClass> newList ;
newList = new List<TestClass>();
//All the items in list1 that are not in list2 are added
foreach (TestClass s in list1)
{
if ( ! list2.Contains(s))
{
newList.Add(s);
}
}
//All the items in list2 that are not in list1 are added
foreach (TestClass s in list2)
{
if ( ! list1.Contains(s))
{
newList.Add(s);
}
}
return newList;
}
And in your class
public class TestClass implements IEquatable
{
protected string teststring;
protected int testint;
public string TestString
{
get { return teststring; }
set { teststring = value; }
}
public int TestInt
{
get { return testint; }
set { testint = value; }
}
public override bool Equals(object y)
{
TestClass newY = y as TestClass;
if (newY == null) { return false; }
return newY.TestString == this.TestString &&
newY.TestInt == this.TestInt;
}
public override int GetHashCode()
{
// use this example or implement some hash code logic
return this.TestInt.GetHashCode() ;
}
public TestClass() { }
}
private static List<TestClass> CompareCodes(List<TestClass> list1,
List<TestClass> list2)
{
List<TestClass> newList = new List<TestClass>();
bool found = false;
foreach (TestClass s in list2)
{
foreach (TestClass t in list1)
{
//let's say that teststring is your object id / key
if(s.TestString==t.TestString )
{
found = true;
break;
}
}
if(!found)
newList.Add(s);
found=false;
}
// do the something for the List1
foreach (TestClass s in list1)
{
foreach (TestClass t in list2)
{
//let's say that teststring is your object id / key
if(s.TestString==t.TestString )
{
found = true;
break;
}
}
if(!found)
newList.Add(s);
found=false;
}
if (newList != null)
return newList;
else
return null;
}
Related
I'm trying to create a simple Linked List that allows me to iterate each element.
But when I try to iterate the list on the method GetReservation() the foreach imidiatly stops and I don't understand why.
I supose that 'this' instruction returns the Enumerable part of the class.
But I'm not sure.
I need some help to figure it out.
public class Reservation
{
public string reference;
public string client;
public string state;
public Reservation(string reference, string client, string state)
{
this.reference = reference;
this.client = client;
this.state = state;
}
public Reservation()
{
}
public override bool Equals(object obj)
{
return obj is Reservation reservation &&
reference == reservation.reference &&
client == reservation.client &&
state == reservation.state;
}
}
public class Row
{
public Reservation reservation;
public Row nextRow;
}
public class Lista : IEnumerable, IEnumerator
{
private Row _header;
private Row Current;
private int counter;
public Lista()
{
_header = Current = null;
counter = 0;
}
public bool MoveNext()
{
if (Current.nextRow != null)
{
Current = Current.nextRow;
return true;
}
else
{
return false;
}
}
public void Reset()
{
Current = _header;
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public IEnumerator GetEnumerator()
{
return this;
}
public void Add(Reservation data)
{
Row newRow = new Row();
newRow.reservation = data;
if (counter == 0)
{
_header = Current = newRow;
} else
{
Current.nextRow = newRow;
Current = newRow;
}
counter++;
}
public Reservation GetReservation(int index)
{
int x = 0;
foreach (Row row in this)
{
if (x == index)
{
return row.reservation;
}
x++;
}
return null;
}
public Reservation Remove(Reservation data)
{
Reservation reservation = null;
if (_header.reservation.Equals(data)){
reservation = _header.reservation;
_header = _header.nextRow;
} else
{
foreach (Row row in this)
{
if (row.nextRow.reservation.Equals(data))
{
if(row.nextRow.nextRow == null)
{
reservation = row.nextRow.reservation;
row.nextRow = null;
} else
{
reservation = row.nextRow.reservation;
row.nextRow = row.nextRow.nextRow;
}
}
}
}
if (reservation != null)
{
counter--;
}
return reservation;
}
}
The main issue is that both the linked list and the enumerator share the current row for different purposes.
When the Add method is called, Current is set to the new row which is the last item of the linked list.
The foreach block in GetReservation calls GetEnumerator which returns the instance of this list. But the current row of the list is the last added row. So when MoveNext is called during the iteration, false is returned since the last row has no next row. That ends the loop.
To solve this problem it is best to create a separate class which implements IEnumerator e.g.
public class Lista : IEnumerable
{
...
GetEnumerator()
{
return new ListaEnumerator(_header);
}
private class ListaEnumerator : IEnumerator
{
private Row _current;
public ListaEnumerator(Row header)
{
_current = header;
}
...
}
}
This way it is possible to have several enumerators that do not affect each other or the list.
You need the IEnumerable implementation to return a new instance of a new class ListEnumerator : IEnumeratorsuch that ListEnumerator will have Current so different instances of ListEnumerator can iterate the list instance independently.
You better of implementing generic versions e.g. IEnumerable<T> and IEnumerator<T>
Usually the IEnumerator implementation is a private class nested under the IEnumerable implementation and it has a data member pointing to the instance of IEnumerable implementation which is being iterated.
I'm writing some UnitTests for a parser and I'm stuck at comparing two List<T> where T is a class of my own, that contains another List<S>.
My UnitTest compares two lists and fails. The code in the UnitTest looks like this:
CollectionAssert.AreEqual(list1, list2, "failed");
I've written a test scenario that should clarify my question:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ComparerTest
{
class Program
{
static void Main(string[] args)
{
List<SimplifiedClass> persons = new List<SimplifiedClass>()
{
new SimplifiedClass()
{
FooBar = "Foo1",
Persons = new List<Person>()
{
new Person(){ ValueA = "Hello", ValueB="Hello"},
new Person(){ ValueA = "Hello2", ValueB="Hello2"},
}
}
};
List<SimplifiedClass> otherPersons = new List<SimplifiedClass>()
{
new SimplifiedClass()
{
FooBar = "Foo1",
Persons = new List<Person>()
{
new Person(){ ValueA = "Hello2", ValueB="Hello2"},
new Person(){ ValueA = "Hello", ValueB="Hello"},
}
}
};
// The goal is to ignore the order of both lists and their sub-lists.. just check if both lists contain the exact items (in the same amount). Basically ignore the order
// This is how I try to compare in my UnitTest:
//CollectionAssert.AreEqual(persons, otherPersons, "failed");
}
}
public class SimplifiedClass
{
public String FooBar { get; set; }
public List<Person> Persons { get; set; }
public override bool Equals(object obj)
{
if (obj == null) { return false;}
PersonComparer personComparer = new PersonComparer();
SimplifiedClass obj2 = (SimplifiedClass)obj;
return this.FooBar == obj2.FooBar && Enumerable.SequenceEqual(this.Persons, obj2.Persons, personComparer); // I think here is my problem
}
public override int GetHashCode()
{
return this.FooBar.GetHashCode() * 117 + this.Persons.GetHashCode();
}
}
public class Person
{
public String ValueA { get; set; }
public String ValueB { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
Person obj2 = (Person)obj;
return this.ValueA == obj2.ValueA && this.ValueB == obj2.ValueB;
}
public override int GetHashCode()
{
if (!String.IsNullOrEmpty(this.ValueA))
{
//return this.ValueA.GetHashCode() ^ this.ValueB.GetHashCode();
return this.ValueA.GetHashCode() * 117 + this.ValueB.GetHashCode();
}
else
{
return this.ValueB.GetHashCode();
}
}
}
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x != null)
{
return x.Equals(y);
}
else
{
return y == null;
}
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
}
The question is strongly related to C# Compare Lists with custom object but ignore order, but I can't find the difference, other than I wrap a list into another object and use the UnitTest one level above.
I've tried to use an IEqualityComparer:
public class PersonComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
if (x != null)
{
return x.Equals(y);
}
else
{
return y == null;
}
}
public int GetHashCode(Person obj)
{
return obj.GetHashCode();
}
}
Afterwards I've tried to implement the ''IComparable'' interface thats allows the objects to be ordered. (Basically like this: https://stackoverflow.com/a/4188041/225808)
However, I don't think my object can be brought into a natural order. Therefore I consider this a hack, if I come up with random ways to sort my class.
public class Person : IComparable<Person>
public int CompareTo(Person other)
{
if (this.GetHashCode() > other.GetHashCode()) return -1;
if (this.GetHashCode() == other.GetHashCode()) return 0;
return 1;
}
I hope I've made no mistakes while simplifying my problem. I think the main problems are:
How can I allow my custom objects to be comparable and define the equality in SimplifiedClass, that relies on the comparision of subclasses (e.g. Person in a list, like List<Person>). I assume Enumerable.SequenceEqual should be replaced with something else, but I don't know with what.
Is CollectionAssert.AreEqual the correct method in my UnitTest?
Equals on a List<T> will only check reference equality between the lists themselves, it does not attempt to look at the items in the list. And as you said you don't want to use SequenceEqual because you don't care about the ordering. In that case you should use CollectionAssert.AreEquivalent, it acts just like Enumerable.SequenceEqual however it does not care about the order of the two collections.
For a more general method that can be used in code it will be a little more complicated, here is a re-implemented version of what Microsoft is doing in their assert method.
public static class Helpers
{
public static bool IsEquivalent(this ICollection source, ICollection target)
{
//These 4 checks are just "shortcuts" so we may be able to return early with a result
// without having to do all the work of comparing every member.
if (source == null != (target == null))
return false; //If one is null and one is not, return false immediately.
if (object.ReferenceEquals((object)source, (object)target) || source == null)
return true; //If both point to the same reference or both are null (We validated that both are true or both are false last if statement) return true;
if (source.Count != target.Count)
return false; //If the counts are different return false;
if (source.Count == 0)
return true; //If the count is 0 there is nothing to compare, return true. (We validated both counts are the same last if statement).
int nullCount1;
int nullCount2;
//Count up the duplicates we see of each element.
Dictionary<object, int> elementCounts1 = GetElementCounts(source, out nullCount1);
Dictionary<object, int> elementCounts2 = GetElementCounts(target, out nullCount2);
//It checks the total number of null items in the collection.
if (nullCount2 != nullCount1)
{
//The count of nulls was different, return false.
return false;
}
else
{
//Go through each key and check that the duplicate count is the same for
// both dictionaries.
foreach (object key in elementCounts1.Keys)
{
int sourceCount;
int targetCount;
elementCounts1.TryGetValue(key, out sourceCount);
elementCounts2.TryGetValue(key, out targetCount);
if (sourceCount != targetCount)
{
//Count of duplicates for a element where different, return false.
return false;
}
}
//All elements matched, return true.
return true;
}
}
//Builds the dictionary out of the collection, this may be re-writeable to a ".GroupBy(" but I did not take the time to do it.
private static Dictionary<object, int> GetElementCounts(ICollection collection, out int nullCount)
{
Dictionary<object, int> dictionary = new Dictionary<object, int>();
nullCount = 0;
foreach (object key in (IEnumerable)collection)
{
if (key == null)
{
++nullCount;
}
else
{
int num;
dictionary.TryGetValue(key, out num);
++num;
dictionary[key] = num;
}
}
return dictionary;
}
}
What it does is it makes a dictionary out of the two collections, counting the duplicates and storing it as the value. It then compares the two dictionaries to make sure that the duplicate count matches for both sides. This lets you know that {1, 2, 2, 3} and {1, 2, 3, 3} are not equal where Enumerable.Execpt would tell you that they where.
I got a lot of data from a database, which are results from a search function. Now I've a List<string[]> which has duplicated elements of type string[]. The string[] in the list are the search results.
I know that every new created array has a different instance so i can't use MyListOfArrays.Distinct().ToList().
Maybe it's a very basic question...
My question is, are there any functions built in to remove a duplicated string[] form the List<string[]>? Or do I have to write it by my selfe?
Thank you
You can use distinct method with custom equalityComparer
IEnumerable<string[]> distinct = inputStringArrayList.Distinct(new EqualityComparer());
EqualityComparer
class EqualityComparer : IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if (x.Length != y.Length)
{
return false;
}
if (x.Where((t, i) => t != y[i]).Any())
{
return false;
}
return true;
}
public int GetHashCode(string[] obj)
{
return obj.GetHashCode();
}
}
Alternative Equals Method
public bool Equals(string[] x, string[] y)
{
return x.SequenceEqual(y);
}
Here I am assuming you are having exact same string arrays with same content at same index.
Correction from Matthew Watson
public int GetHashCode(string[] obj)
{
if (obj == null)
return 0;
int hash = 17;
unchecked
{
foreach (string s in obj)
hash = hash*23 + ((s == null) ? 0 : s.GetHashCode());
}
return hash;
}
I have corrected the answer from #Muctadir Dinar.
(He deserves credit for the answer - I am just correcting it and providing a complete test program):
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
sealed class EqualityComparer: IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if (ReferenceEquals(x, y))
return true;
if (x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(string[] obj)
{
if (obj == null)
return 0;
int hash = 17;
unchecked
{
foreach (string s in obj)
hash = hash*23 + ((s == null) ? 0 : s.GetHashCode());
}
return hash;
}
}
class Program
{
private void run()
{
var list = new List<string[]>
{
strings(1, 10),
strings(2, 10),
strings(3, 10),
strings(2, 10),
strings(4, 10)
};
dump(list);
Console.WriteLine();
var result = list.Distinct(new EqualityComparer());
dump(result);
}
static void dump(IEnumerable<string[]> list)
{
foreach (var array in list)
Console.WriteLine(string.Join(",", array));
}
static string[] strings(int start, int count)
{
return Enumerable.Range(start, count)
.Select(element => element.ToString())
.ToArray();
}
static void Main(string[] args)
{
new Program().run();
}
}
}
A simple and not very efficient approach would be to use string.Join on the string[]:
list = list
.GroupBy(strArr => string.Join("|", strArr))
.Select(g => g.First())
.ToList();
I am using a IDictionary, but it allows one key for one value is there any way or IEnumerable list that I can use to add value with teo keys??
Thanks
you could use anything in the generic Dictionary as key...
for example:
class MyKey /*: IComparable*/ {
public string Key1 {get;set;}
public string Key2 {get;set;}
/* //CompareTo seems to be the wrong thing to implement...
public int CompareTo(object o) {
if(!(o is MyKey))
return -1;
int k1 = Key1.CompareTo(((MyKey)o).Key1);
return k1 == 0 ? Key2.CompareTo(((MyKey)o).Key2) : k1;
}*/
public override bool Equals(object o) {
return (o is MyKey) &&
(Key1 == ((MyKey)o).Key1) &&
(Key2 == ((MyKey)o).Key2);
}
public override int GetHashCode() {
return Key1.GetHashCode() ^ Key2.GetHashCode();
}
//to be very kewl we'll add the (in)equality-op's too...:
public static bool operator ==(MyKey m1, MyKey m2) {
return m1.Equals(m2);
}
public static bool operator !=(MyKey m1, MyKey m2) {
return !m1.Equals(m2);
}
}
Dictionary<MyKey, string> myKewlDictionary...
If you are looking for a way to generate a composite key from two values and you are using .NET 4.0 you can use a Tuple as a key - e.g.
var _myDictionary = new Dictionary<Tuple<int, int>, OtherClass>();
_myDictionary.Add(Tuple.Create(item1.Id, item2.Id), item3);
var item = _myDictionary[Tuple.Create(item1.Id, item2.Id)];
Hmm... Really, I don't know why you need that solution, it seems strange. Anyways, you can use custom IEnumerable class as your key collection.
You can find my test example below.
using System;
using System.Collections;
using System.Collections.Generic;
namespace TestApplication
{
class Program
{
static void Main(string[] args)
{
IDictionary<IEnumerable, object> dictionary1 = new Dictionary<IEnumerable, object>();
IEnumerable key11 = new string[] { "key1", "key2" };
IEnumerable key12 = new string[] { "key1", "key2" };
dictionary1.Add(key11, new object());
// Exception doesn't occur because key11 and key12 are not equal objects
dictionary1.Add(key12, new object());
IDictionary<KeyCollection<string>, object> dictionary2 = new Dictionary<KeyCollection<string>, object>();
KeyCollection<string> key21 = new KeyCollection<string>(new string[] { "key1", "key2" });
KeyCollection<string> key22 = new KeyCollection<string>(new string[] { "key1", "key2" });
dictionary2.Add(key21, new object());
// ArgumentEception: An item with the same key has already been added
dictionary2.Add(key22, new object());
}
private class KeyCollection<T> : IEnumerable where T : class
{
private IEnumerable<T> m_KeyCollection;
public KeyCollection() : this(new List<T>())
{
}
public KeyCollection(IEnumerable<T> array)
{
if (array == null)
{
throw (new NullReferenceException("'array' parameter must be initialized!"));
}
IList<T> list = new List<T>();
IEnumerator<T> enumerator = array.GetEnumerator();
while (enumerator.MoveNext())
{
list.Add(enumerator.Current);
}
m_KeyCollection = list;
}
public IEnumerator GetEnumerator()
{
return m_KeyCollection.GetEnumerator();
}
public override bool Equals(object obj)
{
KeyCollection<T> collection = (obj as KeyCollection<T>);
if (collection == null)
{
return false;
}
IEnumerator<T> enumerator1 = m_KeyCollection.GetEnumerator();
IEnumerator enumerator2 = collection.GetEnumerator();
bool moveNext1 = false, moveNext2 = false;
while (true)
{
moveNext1 = enumerator1.MoveNext();
moveNext2 = enumerator2.MoveNext();
if (moveNext1 && moveNext2)
{
T current1 = enumerator1.Current;
T current2 = (enumerator2.Current as T);
if ((current1 == null) || (current2 == null) || (!current1.Equals(current2)))
{
return false;
}
continue;
}
return ((!moveNext1) && (!moveNext2));
}
}
public override int GetHashCode()
{
IEnumerator<T> enumerator = m_KeyCollection.GetEnumerator();
string stringHash = string.Empty;
while (enumerator.MoveNext())
{
stringHash += string.Format("_{0}", ((enumerator.Current != null) ? enumerator.Current.GetHashCode().ToString() : "-1"));
}
return (string.IsNullOrEmpty(stringHash) ? -1 : stringHash.GetHashCode());
}
}
}
}
If your UserID and SessionID can never collide then you can use them both as hash keys: stuff your Userinfo in the dictionary once with the UserID, once with the SessionID, and in the cases when you only have one or the other, add to the dictionary with the only one you have.
(You may need to be concerned about adding a second key to a Userinfo object; say, if method login has a UserID and is deciding whether it needs to create a new Userinfo object to insert into the dictionary vs looking up a Userinfo object via the UserID, and method returning has already inserted the 'correct' Userinfo object into the dictionary using a SessionID but no UserID, login would incorrectly create a new Userinfo object. This may or may not be an issue for your application.)
If the UserID and SessionID can collide then you could use two dictionaries, and search them sequentially when needed. This might still be cleaner than using one dictionary with two different types of keys.
If you will always have one key and sometimes the other, you could use two different kinds of dictionaries: e.g., one to store UserID -> SessionID, and one for SessionID -> Userinfo; OR, one to store SessionID -> UserID, and one for UserID -> Userinfo. This would let you quickly chain your lookups based on the information available. (It feels more like a relational database.)
I need to create an web module, with this module i need to fetch the title of some web site, after i will find that title i need to store that in thread safe caching mechanism and i need to save there the 10 lat fetched titles.
Any help please ?
Writing some locking code would be fairly easy except for...
How do you want to retrieve it? Do you want to be able to enumerate (foreach) over the list in a thread-safe fashion? There are a number of different ways to do that part, each with trade-offs.
You could go with the default behavior
This probably won't work well -- you'll get an exception if someone changes the list while you are enumerating it.
You could lock the collection during the whole course of the enumeration. This means that any thread attempting to add to your cache will be blocked until the foreach loop exits.
You could copy the collection internally each time you enumerate it and enumerate the copy.
This means that if someone adds to your list while you are enumerating it, you won't "see" the change.
For a list of ten, I'd go with the last option. (copy internally).
You code would look something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Enumerator
{
class Program
{
static void Main(string[] args)
{
MyCache<string> cache = new MyCache<string>();
cache.Add("test");
foreach (string item in cache)
Console.WriteLine(item);
Console.ReadLine();
}
}
public class MyCache<T>: System.Collections.IEnumerable
{
private readonly LinkedList<T> InternalCache = new LinkedList<T>();
private readonly object _Lock = new Object();
public void Add(T item)
{
lock (_Lock)
{
if (InternalCache.Count == 10)
InternalCache.RemoveLast();
InternalCache.AddFirst(item);
}
}
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
// copy the internal cache to an array. We'll really be enumerating that array
// our enumeration won't block subsequent calls to Add, but this "foreach" instance won't see Adds either
lock (_Lock)
{
T[] enumeration = new T[InternalCache.Count];
InternalCache.CopyTo(enumeration, 0);
return enumeration.GetEnumerator();
}
}
#endregion
}
}
<B>EDIT 1:</B>
After sharing some comments with Rob Levine (below), I thought I'd throw a couple other alternatives out there.
This version allows you to iterate the collection lock-free. However, the Add() method is a little more expensive, as it must copy the list (moved the expense off of the Enumerate, and onto the add).
public class Cache2<T>: IEnumerable<T>
{
// changes occur to this list, and it is copied to ModifyableList
private LinkedList<T> ModifyableList = new LinkedList<T>();
// This list is the one that is iterated by GetEnumerator
private volatile LinkedList<T> EnumeratedList = new LinkedList<T>();
private readonly object LockObj = new object();
public void Add(T item)
{
// on an add, we swap out the list that is being enumerated
lock (LockObj)
{
if (this.ModifyableList.Count == 10)
this.ModifyableList.RemoveLast();
this.ModifyableList.AddFirst(item);
this.EnumeratedList = this.ModifyableList;
// the copy needs to happen within the lock, so that threaded calls to Add() remain consistent
this.ModifyableList = new LinkedList<T>(this.ModifyableList);
}
}
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
IEnumerable<T> enumerable = this.EnumeratedList;
return enumerable.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
System.Collections.IEnumerable enumerable = this.EnumeratedList;
return enumerable.GetEnumerator();
}
#endregion
}
<B>Edit 2:</B>
In the last example, we had a really inexpensive iteration, with the trade-off being a more expensive call to Add(). Next, I thought about using a ReaderWriterLockSlim (this is a .Net 3.5 object -- the old ReaderWriterLock offered pretty poor performance)
With this model, the Add() method is less expensive than the previous model (although Add still has to take an exclusive lock). With this model, we don't have to create copies of the list. When we enumerate the list, we enter a readlock, which does not block other readers, but does block/is blocked by writers (calls to Add). As to which model is better -- it probably depends upon how you are using the cache. I would recommend Testing and measuring.
public class Cache3<T> : IEnumerable<T>
{
private LinkedList<T> InternalCache = new LinkedList<T>();
private readonly System.Threading.ReaderWriterLockSlim LockObj = new System.Threading.ReaderWriterLockSlim();
public void Add(T item)
{
this.LockObj.EnterWriteLock();
try
{
if(this.InternalCache.Count == 10)
this.InternalCache.RemoveLast();
this.InternalCache.AddFirst(item);
}
finally
{
this.LockObj.ExitWriteLock();
}
}
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
this.LockObj.EnterReadLock();
try
{
foreach(T item in this.InternalCache)
yield return item;
}
finally
{
this.LockObj.ExitReadLock();
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
this.LockObj.EnterReadLock();
try
{
foreach (T item in this.InternalCache)
yield return item;
}
finally
{
this.LockObj.ExitReadLock();
}
}
#endregion
}
you might want to read up on this technique. Read-copy-update (RCU).
Posted this same answer over at: Thread-safe cache libraries for .NET
I know your pain as I am one of the Architects of Dedoose. I have messed around with a lot of caching libraries and ended up building this one after much tribulation. The one assumption for this Cache Manager is that all collections stored by this class implement an interface to get a Guid as a "Id" property on each object. Being that this is for a RIA it includes a lot of methods for adding /updating /removing items from these collections.
Here's my CollectionCacheManager
public class CollectionCacheManager
{
private static readonly object _objLockPeek = new object();
private static readonly Dictionary<String, object> _htLocksByKey = new Dictionary<string, object>();
private static readonly Dictionary<String, CollectionCacheEntry> _htCollectionCache = new Dictionary<string, CollectionCacheEntry>();
private static DateTime _dtLastPurgeCheck;
public static List<T> FetchAndCache<T>(string sKey, Func<List<T>> fGetCollectionDelegate) where T : IUniqueIdActiveRecord
{
List<T> colItems = new List<T>();
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.Keys.Contains(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
colItems = (List<T>) objCacheEntry.Collection;
objCacheEntry.LastAccess = DateTime.Now;
}
else
{
colItems = fGetCollectionDelegate();
SaveCollection<T>(sKey, colItems);
}
}
List<T> objReturnCollection = CloneCollection<T>(colItems);
return objReturnCollection;
}
public static List<Guid> FetchAndCache(string sKey, Func<List<Guid>> fGetCollectionDelegate)
{
List<Guid> colIds = new List<Guid>();
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.Keys.Contains(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
colIds = (List<Guid>)objCacheEntry.Collection;
objCacheEntry.LastAccess = DateTime.Now;
}
else
{
colIds = fGetCollectionDelegate();
SaveCollection(sKey, colIds);
}
}
List<Guid> colReturnIds = CloneCollection(colIds);
return colReturnIds;
}
private static List<T> GetCollection<T>(string sKey) where T : IUniqueIdActiveRecord
{
List<T> objReturnCollection = null;
if (_htCollectionCache.Keys.Contains(sKey) == true)
{
CollectionCacheEntry objCacheEntry = null;
lock (GetKeyLock(sKey))
{
objCacheEntry = _htCollectionCache[sKey];
objCacheEntry.LastAccess = DateTime.Now;
}
if (objCacheEntry.Collection != null && objCacheEntry.Collection is List<T>)
{
objReturnCollection = CloneCollection<T>((List<T>)objCacheEntry.Collection);
}
}
return objReturnCollection;
}
public static void SaveCollection<T>(string sKey, List<T> colItems) where T : IUniqueIdActiveRecord
{
CollectionCacheEntry objCacheEntry = new CollectionCacheEntry();
objCacheEntry.Key = sKey;
objCacheEntry.CacheEntry = DateTime.Now;
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
objCacheEntry.Collection = CloneCollection(colItems);
lock (GetKeyLock(sKey))
{
_htCollectionCache[sKey] = objCacheEntry;
}
}
public static void SaveCollection(string sKey, List<Guid> colIDs)
{
CollectionCacheEntry objCacheEntry = new CollectionCacheEntry();
objCacheEntry.Key = sKey;
objCacheEntry.CacheEntry = DateTime.Now;
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
objCacheEntry.Collection = CloneCollection(colIDs);
lock (GetKeyLock(sKey))
{
_htCollectionCache[sKey] = objCacheEntry;
}
}
public static void UpdateCollection<T>(string sKey, List<T> colItems) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.ContainsKey(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
objCacheEntry.Collection = new List<T>();
//Clone the collection before insertion to ensure it can't be touched
foreach (T objItem in colItems)
{
objCacheEntry.Collection.Add(objItem);
}
_htCollectionCache[sKey] = objCacheEntry;
}
else
{
SaveCollection<T>(sKey, colItems);
}
}
}
public static void UpdateItem<T>(string sKey, T objItem) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.ContainsKey(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
List<T> colItems = (List<T>)objCacheEntry.Collection;
colItems.RemoveAll(o => o.Id == objItem.Id);
colItems.Add(objItem);
objCacheEntry.Collection = colItems;
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
}
}
}
public static void UpdateItems<T>(string sKey, List<T> colItemsToUpdate) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
if (_htCollectionCache.ContainsKey(sKey) == true)
{
CollectionCacheEntry objCacheEntry = _htCollectionCache[sKey];
List<T> colCachedItems = (List<T>)objCacheEntry.Collection;
foreach (T objItem in colCachedItems)
{
colCachedItems.RemoveAll(o => o.Id == objItem.Id);
colCachedItems.Add(objItem);
}
objCacheEntry.Collection = colCachedItems;
objCacheEntry.LastAccess = DateTime.Now;
objCacheEntry.LastUpdate = DateTime.Now;
}
}
}
public static void RemoveItemFromCollection<T>(string sKey, T objItem) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
List<T> objCollection = GetCollection<T>(sKey);
if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) > 0)
{
objCollection.RemoveAll(o => o.Id == objItem.Id);
UpdateCollection<T>(sKey, objCollection);
}
}
}
public static void RemoveItemsFromCollection<T>(string sKey, List<T> colItemsToAdd) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
Boolean bCollectionChanged = false;
List<T> objCollection = GetCollection<T>(sKey);
foreach (T objItem in colItemsToAdd)
{
if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) > 0)
{
objCollection.RemoveAll(o => o.Id == objItem.Id);
bCollectionChanged = true;
}
}
if (bCollectionChanged == true)
{
UpdateCollection<T>(sKey, objCollection);
}
}
}
public static void AddItemToCollection<T>(string sKey, T objItem) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
List<T> objCollection = GetCollection<T>(sKey);
if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) == 0)
{
objCollection.Add(objItem);
UpdateCollection<T>(sKey, objCollection);
}
}
}
public static void AddItemsToCollection<T>(string sKey, List<T> colItemsToAdd) where T : IUniqueIdActiveRecord
{
lock (GetKeyLock(sKey))
{
List<T> objCollection = GetCollection<T>(sKey);
Boolean bCollectionChanged = false;
foreach (T objItem in colItemsToAdd)
{
if (objCollection != null && objCollection.Count(o => o.Id == objItem.Id) == 0)
{
objCollection.Add(objItem);
bCollectionChanged = true;
}
}
if (bCollectionChanged == true)
{
UpdateCollection<T>(sKey, objCollection);
}
}
}
public static void PurgeCollectionByMaxLastAccessInMinutes(int iMinutesSinceLastAccess)
{
DateTime dtThreshHold = DateTime.Now.AddMinutes(iMinutesSinceLastAccess * -1);
if (_dtLastPurgeCheck == null || dtThreshHold > _dtLastPurgeCheck)
{
lock (_objLockPeek)
{
CollectionCacheEntry objCacheEntry;
List<String> colKeysToRemove = new List<string>();
foreach (string sCollectionKey in _htCollectionCache.Keys)
{
objCacheEntry = _htCollectionCache[sCollectionKey];
if (objCacheEntry.LastAccess < dtThreshHold)
{
colKeysToRemove.Add(sCollectionKey);
}
}
foreach (String sKeyToRemove in colKeysToRemove)
{
_htCollectionCache.Remove(sKeyToRemove);
}
}
_dtLastPurgeCheck = DateTime.Now;
}
}
public static void ClearCollection(String sKey)
{
lock (GetKeyLock(sKey))
{
lock (_objLockPeek)
{
if (_htCollectionCache.ContainsKey(sKey) == true)
{
_htCollectionCache.Remove(sKey);
}
}
}
}
#region Helper Methods
private static object GetKeyLock(String sKey)
{
//Ensure even if hell freezes over this lock exists
if (_htLocksByKey.Keys.Contains(sKey) == false)
{
lock (_objLockPeek)
{
if (_htLocksByKey.Keys.Contains(sKey) == false)
{
_htLocksByKey[sKey] = new object();
}
}
}
return _htLocksByKey[sKey];
}
private static List<T> CloneCollection<T>(List<T> colItems) where T : IUniqueIdActiveRecord
{
List<T> objReturnCollection = new List<T>();
//Clone the list - NEVER return the internal cache list
if (colItems != null && colItems.Count > 0)
{
List<T> colCachedItems = (List<T>)colItems;
foreach (T objItem in colCachedItems)
{
objReturnCollection.Add(objItem);
}
}
return objReturnCollection;
}
private static List<Guid> CloneCollection(List<Guid> colIds)
{
List<Guid> colReturnIds = new List<Guid>();
//Clone the list - NEVER return the internal cache list
if (colIds != null && colIds.Count > 0)
{
List<Guid> colCachedItems = (List<Guid>)colIds;
foreach (Guid gId in colCachedItems)
{
colReturnIds.Add(gId);
}
}
return colReturnIds;
}
#endregion
#region Admin Functions
public static List<CollectionCacheEntry> GetAllCacheEntries()
{
return _htCollectionCache.Values.ToList();
}
public static void ClearEntireCache()
{
_htCollectionCache.Clear();
}
#endregion
}
public sealed class CollectionCacheEntry
{
public String Key;
public DateTime CacheEntry;
public DateTime LastUpdate;
public DateTime LastAccess;
public IList Collection;
}
Here is an example of how I use it:
public static class ResourceCacheController
{
#region Cached Methods
public static List<Resource> GetResourcesByProject(Guid gProjectId)
{
String sKey = GetCacheKeyProjectResources(gProjectId);
List<Resource> colItems = CollectionCacheManager.FetchAndCache<Resource>(sKey, delegate() { return ResourceAccess.GetResourcesByProject(gProjectId); });
return colItems;
}
#endregion
#region Cache Dependant Methods
public static int GetResourceCountByProject(Guid gProjectId)
{
return GetResourcesByProject(gProjectId).Count;
}
public static List<Resource> GetResourcesByIds(Guid gProjectId, List<Guid> colResourceIds)
{
if (colResourceIds == null || colResourceIds.Count == 0)
{
return null;
}
return GetResourcesByProject(gProjectId).FindAll(objRes => colResourceIds.Any(gId => objRes.Id == gId)).ToList();
}
public static Resource GetResourceById(Guid gProjectId, Guid gResourceId)
{
return GetResourcesByProject(gProjectId).SingleOrDefault(o => o.Id == gResourceId);
}
#endregion
#region Cache Keys and Clear
public static void ClearCacheProjectResources(Guid gProjectId)
{ CollectionCacheManager.ClearCollection(GetCacheKeyProjectResources(gProjectId));
}
public static string GetCacheKeyProjectResources(Guid gProjectId)
{
return string.Concat("ResourceCacheController.ProjectResources.", gProjectId.ToString());
}
#endregion
internal static void ProcessDeleteResource(Guid gProjectId, Guid gResourceId)
{
Resource objRes = GetResourceById(gProjectId, gResourceId);
if (objRes != null)
{ CollectionCacheManager.RemoveItemFromCollection(GetCacheKeyProjectResources(gProjectId), objRes);
}
}
internal static void ProcessUpdateResource(Resource objResource)
{
CollectionCacheManager.UpdateItem(GetCacheKeyProjectResources(objResource.Id), objResource);
}
internal static void ProcessAddResource(Guid gProjectId, Resource objResource)
{
CollectionCacheManager.AddItemToCollection(GetCacheKeyProjectResources(gProjectId), objResource);
}
}
Here's the Interface in question:
public interface IUniqueIdActiveRecord
{
Guid Id { get; set; }
}
Hope this helps, I've been through hell and back a few times to finally arrive at this as the solution, and for us It's been a godsend, but I cannot guarantee that it's perfect, only that we haven't found an issue yet.