/* Copyright (c) 2003-2007 Niels Kokholm and Peter Sestoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // C5 example: LockstepCollections: A group collections lockstep, so // that they all contain the same items. This is done by equipping // them all with event handlers, so that when one collection is // updated (item added or removed) then all the others are updated // too. The collections must be initially empty. // The idea is to organize the N collections c0, ..., c(N-1) in a ring // in which c(i) is followed by c((i+1)%N), and then each collection // with event handlers that update the next collection. The update to // that collection in turn will cause its event listeners to update // the next next collection, and so on. // Event cycles are prevented by using a common counter "steps", // initialized to the number N of collections, and reset to N by // CollectionChanged events. All other events decrement the counter // and performing further updates only if it is positive. Since the // CollectionChanged events are fired only after all the other events, // the counter will be reset only after all the updates have been // performed, but then it will (superfluously) be reset N times. None // of this will work in a multithreaded setting, of course; but then // even plain updates wouldn't either. // If the (equality) comparer of one collection identifies more items // than that of another collection, then the lockstep collections do // not necessarily have the same number of items, even though they all // started out empty. This also means that it matters what collection // one attempts to add an item to, and in what order the collections // are chained. If one adds a new item to a collection that contains // one deemed equal to it, then nothing happens, the event is not // raised, and the item will not be added to any of the collections. // If instead one adds it to a collection where it is not deemed equal // to an existing item, it will be added to that collection but // possibly not to others. // Compile with // csc /r:C5.dll LockstepCollections.cs using System; // Console using C5; using SCG = System.Collections.Generic; namespace LockstepCollections { static class LockstepCollections { static void Main(String[] args) { ICollection nameColl = new HashSet(new Person.NameEqualityComparer()); ICollection dateColl = new TreeSet(new Person.DateComparer()); MakeLockstep(nameColl, dateColl); Person p1 = new Person("Peter", 19620625), p2 = new Person("Carsten", 19640627), p3 = new Person("Carsten", 19640628); nameColl.Add(p1); nameColl.Add(p2); dateColl.Add(p3); Console.WriteLine("dateColl = {0}", dateColl); Console.WriteLine("nameColl = {0}", nameColl); dateColl.Remove(p1); Console.WriteLine("dateColl = {0}", dateColl); Console.WriteLine("nameColl = {0}", nameColl); dateColl.Clear(); Console.WriteLine("dateColl = {0}", dateColl); Console.WriteLine("nameColl = {0}", nameColl); } static void MakeLockstep(params ICollection[] colls) { // These will be captured in the event handler delegates below int N = colls.Length; int steps = N; for (int i=0; i thisColl = colls[i]; ICollection nextColl = colls[(i+1)%N]; thisColl.CollectionChanged += delegate(Object coll) { steps = N; }; thisColl.CollectionCleared += delegate(Object coll, ClearedEventArgs args) { // For now ignoring that the clearing may be partial if (--steps > 0) { nextColl.Clear(); } }; thisColl.ItemsAdded += delegate(Object coll, ItemCountEventArgs args) { // For now ignoring the multiplicity if (--steps > 0) { T item = args.Item; nextColl.FindOrAdd(ref item); } }; thisColl.ItemsRemoved += delegate(Object coll, ItemCountEventArgs args) { // For now ignoring the multiplicity if (--steps > 0) { nextColl.Remove(args.Item); } }; } } } public class Person { String name; int date; public Person(String name, int date) { this.name = name; this.date = date; } public class NameEqualityComparer : SCG.IEqualityComparer { public bool Equals(Person p1, Person p2) { return p1.name == p2.name; } public int GetHashCode(Person p) { return p.name.GetHashCode(); } } public class DateComparer : SCG.IComparer { public int Compare(Person p1, Person p2) { return p1.date.CompareTo(p2.date); } } public override String ToString() { return name + " (" + date + ")"; } } }