.Net Collections
Interfaces
The collection/list interfaces
- IEnumerable (and IEnumerable<T>) Allows you to "foreach".
public IEnumerable GetEnumerator()
{
foreach (ItemObject itemObject in _list)
yield return itemObject;
} - ICollection inherits IEnumerable and adds Count and ability to lock SyncRoot
- IList inherits ICollection and adds Add/Remove/Clear/Insert/Contains + IsFixedSize/IsReadOnly if needed
- IDictionary inherits ICollection and is the name/value version of IList
The functional interfaces
- IComparer - for sorting
- IEnumerator - used for IEnumerable (but you can skip it with yield return)
Collections
Implement IList and/or IDictionary. For dictionaries (name/values):
- foreach DictionaryEntry (in Generic versions, NameValuePair/ KeyValuePair)
- Access by [index] as well as [key]: SortedList and OrderedDictionary (latter isn't sorted!)
- By size: Hashtable (big), ListDictionary (small), HybridDictionary (both)
- For strings, Hashtable and SortedList are case sensitive (Specialized.StringDictionary is insensitive). Use Specialized.CollectionsUtil.CreateCaseInsensitiveHashtable() or SortedList()
NB: collections ctors can take StringComparer.InvarientCulture - Specialized.StringDictionary and BaseDictionary for 1.1; now easier with generics
- Specialized.NameValueCollection is like StringDictionary but has multiple keys (only thru .Add, not [indexer]- .Add("key1","a");.Add("key1","b"); adds two values. [indexer] returns CSV of all keys)
- Generic dictionaries are simpler = Dictionary<T, V> (most things) and SortedList<T, V>.
- SortedList and SortedDictionary are almost identical: the list one is more memory efficient and faster to initially load; dictionary is faster to insert/remove
Implementing interfaces
- Sort: Implement IComparable (GetComparer static method, CompareTo instance method) and IComparer (which calls CompareTo on the instances, normally a nested class). Can pass in a generic delegate (a typed sort method can be passed in that matches the generic signature)
- See my GenericComparer<T> using reflection
- Duplicate objects- override .GetHashCode to return the unique property, AND override object.Equals Or create an IEqualityComparer (which also has .GetHashCode and .Equals). You can't just hash all the properties, because if you change one, it's a "different" object.
- BitArray is an array of booleans (doesn't dynamically resize- but you can reset .Length)
- Specialized.BitVector32 is a structure to hide binary maths with a single Int32.
//1: lots of booleans. Use masks.
BitVector32 vector = new BitVector32(0);// All bit flags set to FALSE.
int mask1 = BitVector32.CreateMask();
int mask2 = BitVector32.CreateMask(mask1);
vector[mask2] = true; //masks allow you to flip booleans by indexerYou can also pack numbers into a single Int32.
//2: Pack numbers using CreateSection
BitVector32 packedVector = new BitVector32();
BitVector32.Section sec1 = BitVector32.CreateSection(20);
BitVector32.Section sec2 = BitVector32.CreateSection(300, sec1);
packedVector[sec1] = 6; //you have 2+ numbers stored in one 32bit int
packedVector[sec2] = 199;
Custom Collections
- Inherit from Collection<T> and override InsertItem/SetItem and ClearItem. (Non generic use CollectionBase - my version)
Thread Safe
You can get a synchronised wrapper around a Stack: Stack mySyncdStack = Stack.Synchronized( myStack )
but for derived classes lock(myCollection.SyncRoot)
Lists with Delegates
Very useful functional programming.
List.Find(Predicate<T>) List.FindAll(Predicate<T>) |
items.Find( delegate(DatedItem d) { return (d.Id == key); }); |
List.Sort(Comparison<T>) | items.Sort( delegate(DatedItem p1, DatedItem p2) { return p1.Name.CompareTo(p2.Name); }); |
List.ConvertAll<TOut>(Comparison<T, TOut>) | items.ConvertAll<Object>( delegate(DatedItem p1) { return (Object)p1; }); |
Sorting
See SortableBindingList<T> (for Windows.Forms)
Copying to an array to sort with a delegate (here, sorting ListItems in asp DropDownList)
private void SortListControl(ListControl ddl)
{
ListItemCollection c = ddl.Items;
ListItem[] items = new ListItem[c.Count];
c.CopyTo(items, 0);
Array.Sort(items, delegate(ListItem a, ListItem b)
{
return a.Text.CompareTo(b.Text);
});
ddl.Items.Clear();
ddl.Items.AddRange(items);
}
Sorting with nullable properties has a slighly more involved delegate
private void SortList()
{
//DeatedObject.Date is a nullable date
List<DatedObject> olist = new List<DatedObject>();
olist.Add(new DatedObject(DateTime.Now.AddDays(1)));
olist.Add(new DatedObject(DateTime.Now));
olist.Add(new DatedObject()); //null date
olist.Sort(CompareDatedObjects); //IList does not have a Sort
comboBox1.DataSource = olist;
comboBox1.DisplayMember = "Date";
}
private static int CompareDatedObjects(DatedObject x, DatedObject y)
{
if (x.Date == null)
return (y.Date == null) ? 0 : -1; //If x is null and y is not null, y is greater.
else // If x is not null...
{
if (y.Date == null)
return 1; // y is null so x is greater.
else
return x.Date.Value.CompareTo(y.Date); // both non-null, compare the properties
}
}
Nongeneric to generic ILists
When using an older style library in .net 2
/// <summary>
/// Converts nongeneric list to generic list.
/// Just a generic way of doing foreach (string s in untypedList) genericList.Add(s);
/// </summary>
/// <typeparam name="T">The type.
/// If any in the list aren't the type, throws an exception</typeparam>
/// <param name="list">The untyped list.</param>
/// <returns>A generic list</returns>
/// <example><code>IList<string> typedList = ConvertToGenericList<string>(untypedList);
/// </code></example>
/// <exception cref="InvalidCastException"/>
private static List<T> ConvertToGenericList<T>(ICollection list)
{
//copy it into a concrete ArrayList
ArrayList untypedList = new ArrayList(list);
//then create the generic list from the arrayList's ToArray
//with funky typeof/as [] casting
return new List<T>(untypedList.ToArray(typeof (T)) as T[]);
}
Linq
//simple join
var simpleJoin = from category in categories
//joins use "equals" not "=="
join product in products on category.Id equals product.CategoryId
orderby category.Id
select new { category.CategoryName, product.ProductName };
//group join (hierarchical outer join)
var groupJoin = from category in categories
join product in products on category.Id equals product.Id
//"into"
into productsForCategory
select new { category.Id, Products = productsForCategory };
//groupby - products by category
var groups = from product in products
group product by product.CategoryId;
Duplicates
//find duplicates
var dupes = from category in categories
group category by category.Id
into categoryGroup
where categoryGroup.Count() > 1
select categoryGroup.Key;
var noDupes = categories.Distinct(); //if you have IEqualityComparer
//or without a comparer
var noDupes2 = from category in categories
//join to a distinct list of ids
join id in categories.Select(x => x.Id).Distinct()
on category.Id equals id
select category;
var noDupes3 = from category in categories
//group them and just get the first
group category by category.Id into categoryGroup
select categoryGroup.First();
Set operations
var inBoth = list1.Intersect(list2);
Log("In both", inBoth);
var inList1Only = list1.Except(list2);
Log("Only in list 1", inList1Only);
var union = list1.Union(list2); //compare list1.Concat(list2)
Log("Both lists merged", union);
//.net 4 merge
var zip = list1.Zip(list2, (a, b) => a + " " + b);
Log("Zipped together", zip);
bool isInSameOrder = list1.SequenceEqual(list2);