Monday, November 20, 2006

DynamicClassFactory - Configuration Handling

(Continuing my previous post)

The dynamically supported classes must be configured. So we need to read them from the config file:


<?xml version="1.0" encoding="utf-8" ?>
<
configuration>
<
configSections>
<
section name="DynamicClassFactoryItemList" type="DynamicClassFactory.DynamicClassFactorySectionHandler, DynamicClassFactory" />
</
configSections>
<
appSettings></appSettings>

<
DynamicClassFactoryItemList>
<
DynamicClassFactoryItem type="DynamicClassFactory.DynamicClassFactoryItem, DynamicClassFactory">
<
Key>0</Key>
<
ClassName>MyNamespace.MyClass</ClassName>
<
AssemblyName>c:\MyPath\MyClassAssembly.dll</AssemblyName>
<
ReferenceLibs>
<
ReferenceLib>c:\MyReferences</ReferenceLib>
</
ReferenceLibs>
</
DynamicClassFactoryItem>
</
DynamicClassFactoryItemList>
</
configuration>

 

/// <summary>
///
Summary description for DynamicClassFactoryItem.
/// </summary>
[Serializable]
public class DynamicClassFactoryItem
{
private int key;
private string className;
private string assemblyName;
private ArrayList referenceLibs;

public DynamicClassFactoryItem()
{
this.referenceLibs = new ArrayList();
}

public DynamicClassFactoryItem(DynamicClassFactoryItem other)
{
this.key = other.key;
this.className = other.className;
this.assemblyName = other.assemblyName;
this.referenceLibs = other.referenceLibs;
}

public DynamicClassFactoryItem(int key, string className, string assemblyName)
{
this.key = key;
this.className = className;
this.assemblyName = assemblyName;
this.referenceLibs = new ArrayList();
}

public DynamicClassFactoryItem(int key, string className, string assemblyName, ArrayList referenceLibs)
{
this.key = key;
this.className = className;
this.assemblyName = assemblyName;
this.referenceLibs = referenceLibs;
}

public DynamicClassFactoryItem(int key, string className, string assemblyName, params object[] referenceLibs)
{
this.key = key;
this.className = className;
this.assemblyName = assemblyName;
if (referenceLibs.Length > 0)
{
this.referenceLibs = new ArrayList(referenceLibs);
}
}

public int Key
{
get { return key; }
set { key = value; }
}

public string ClassName
{
get { return className; }
set { className= value; }
}

public string AssemblyName
{
get { return assemblyName; }
set { assemblyName = value; }
}

public ArrayList ReferenceLibs
{
get { return referenceLibs; }
set { referenceLibs = value; }
}
}


/// <summary>
///
Reads the portion of a configuration file that holds DynamicClassFactory information
/// </summary>
public class DynamicClassFactorySectionHandler : IConfigurationSectionHandler
{
public DynamicClassFactorySectionHandler() : base()
{
}

public object Create(object parent, object configContext, XmlNode section)
{
DynamicClassFactoryItem[] items = new DynamicClassFactoryItem[0];

try
{
XmlNodeList nodes = section.SelectNodes("DynamicClassFactoryItem");
items =
new DynamicClassFactoryItem[nodes.Count];
foreach (XmlNode node in nodes)
{
XPathNavigator xNav=node.CreateNavigator();
string typeOfObject=(string) xNav.Evaluate("string(@type)");
Type t=Type.GetType(typeOfObject);
XmlSerializer ser=new XmlSerializer(t);
XmlNodeReader xNodeReader=new XmlNodeReader(node);
DynamicClassFactoryItem current = (DynamicClassFactoryItem)ser.Deserialize(xNodeReader);
XmlNodeList refLibs = node.SelectNodes(@"ReferenceLibs/ReferenceLib");
foreach (XmlNode refLib in refLibs)
{
current.ReferenceLibs.Add(refLib.InnerText);
}
items[current.Key] = current;
}
}
catch (Exception e)
{
Debug.WriteLine(string.Format("An exception occurred in DynamicClassFactorySectionHandler.Create: {0}", e.Message));
throw;
}

return items;
}
}

DynamicClassFactory - The Heart

(Continuing my previous post)

The heart of DCF is the Reflection-based bit that generates a helper class for each of the supported classes. That's all done in the init() method hereunder:


/// <summary>
///
Initializes the ClassFactory.
/// Creates the code for the ClassFactory and compiles it.
/// If for any reason the ClassFactory could not be properly created, an exception is fired.
/// </summary>
private static void init()
{
try
{
lock (items.SyncRoot)
{
CSharpCodeProvider cp = new CSharpCodeProvider();
//ICodeCompiler ic = cp.CreateCompiler();
CodeDomProvider cdp = new CSharpCodeProvider();
CompilerParameters cpar = new CompilerParameters();
cpar.GenerateInMemory =
true;
cpar.GenerateExecutable =
false;

Hashtable assemblies = new Hashtable();

// Add default references
cpar.ReferencedAssemblies.Add("system.dll");
cpar.ReferencedAssemblies.Add(
"DynamicClassFactory.dll");

foreach (DynamicClassFactoryItem item in items)
{
// Add the reference libraries to the compiler options
// This was added to support cases that referenced libraries are not located
// in the current directory. It became necessary when using the class factory
// from within a Service, in which case the current directory is not the
// directory of the currently running assembly (but System32)
// IMPORTANT: I you want to use this class from within a service, you must have
// entered the folders where to look for assemblies in the config file as so:
// <ReferenceLibs>
// <ReferenceLib>T:\Src\TSSrv\bin\Debug</ReferenceLib>
// </ReferenceLibs>

foreach (string refLib in item.ReferenceLibs)
{
cpar.CompilerOptions +=
" /lib:\"" + refLib + "\"";
}

// Load the assemblies - will enable us to get type-information
Assembly currentAssembly = (Assembly)assemblies[item.AssemblyName];

if (currentAssembly == null)
{
currentAssembly =
Assembly.LoadFrom(item.AssemblyName);

assemblies[item.AssemblyName] = currentAssembly;
if (currentAssembly == null)
{
throw new Exception(string.Format("Assembly with name {0} could not be loaded by DynamicClassFactory", item.AssemblyName));
}

// Set references:
// 1. First we must add the current assembly to the references
// 2. Then we must add all referenced assemblies as well
// NOTE: We explicitely do NOT use cpar.ReferencedAssemblies because
// it does not handle well cases that the assembly name has blanks in it
cpar.CompilerOptions += " /reference:\"" + item.AssemblyName + "\"";

Debug.WriteLine("Adding " + currentAssembly.GetReferencedAssemblies().Length.ToString() + " referenced assemblies for " + item.AssemblyName);

foreach (AssemblyName assemblyName in currentAssembly.GetReferencedAssemblies())
{
if (!cpar.ReferencedAssemblies.Contains("\"" + assemblyName.Name + ".dll\""))
{
Debug.WriteLine("Adding reference assembly - \"" + assemblyName.Name + ".dll\"");
cpar.CompilerOptions +=
" /reference:\"" + assemblyName.Name + ".dll\"";
}
}

Debug.WriteLine("All referenced assemblies for " + item.AssemblyName + " were added successfully");
}
}

string newLine = Environment.NewLine;
string src =
"using System;" + newLine +
"using DynamicClassFactory;" + newLine +
"class ClassFactoryImpl : DynamicClassFactory.IClassFactory" + newLine +
"{" + newLine +
"public ClassFactoryImpl(){}" + newLine +
"public object CreateInstance(int id, params object[] args)" + newLine +
"{" + newLine +
"switch (id)" + newLine +
"{" + newLine;
foreach (DynamicClassFactoryItem item in items)
{
src +=
"case " + Convert.ToInt32(item.Key) + ":" + newLine +
"return " + item.ClassName.Replace(".", "_") + "ClassCreator.Create(args);" + newLine;
}

src +=
"default:" + newLine +
"return null;" + newLine +
"}" + newLine +
"}" + newLine;

// Create an internal class for each supported item in the factory.
foreach (DynamicClassFactoryItem item in items)
{
src +=
"class " + item.ClassName.Replace(".", "_") + "ClassCreator " + newLine +
"{" + newLine +
"public static object Create(params object[] args)" + newLine +
"{" + newLine;
Assembly a = (Assembly)assemblies[item.AssemblyName];
Type currentType = null;
foreach (Type t in a.GetTypes())
{
if (t.FullName == item.ClassName)
{
currentType = t;
break;
}
}

HybridDictionary parametersByLength = new HybridDictionary(currentType.GetConstructors().Length);

// First we organize the various ctor parameters according to the number of parameters in it
foreach (ConstructorInfo cInfo in currentType.GetConstructors())
{
ParameterInfo[] parameters = cInfo.GetParameters();
ArrayList parametersList = parametersByLength[parameters.Length] as ArrayList;
if (parametersList == null)
{
parametersList =
new ArrayList();
parametersByLength[parameters.Length] = parametersList;
}
parametersList.Add(parameters);
}

// Now, for each size of ctor, we build simple if-predicates.
// This may not be the best-performing solution (e.g. if there are many ctors with
// 10 parameters and they differ only by the type of the last parameter),
// but in most cases it's more than enough.
// Another approach would be to build a decision tree, such that each possible ctor
// would end in one of the tree's leaves. However, it would significantly increase
// the implementation complexity, and would probably do little to improve performance.
foreach (int parametersCount in parametersByLength.Keys)
{
ArrayList parametersList = parametersByLength[parametersCount] as ArrayList;
src +=
"if (args.Length == " + parametersCount.ToString() + ")" + newLine +
"{" + newLine;

if (parametersCount == 0)
{
src +=
"return new " + item.ClassName + "();" + newLine;
}
else
{
foreach (ParameterInfo[] parameters in parametersList)
{
string argsList = "";
src +=
"if (";
for (int i = 0; i < parameters.Length; i++)
{
ParameterInfo pInfo = parameters[i];
src +=
"(args[" + i.ToString() + "] is " + pInfo.ParameterType.ToString() + ")";
if (i != parameters.Length-1)
{
src +=
" && ";
}

if (i != 0)
{
argsList +=
", ";
}
argsList +=
"(" + pInfo.ParameterType.ToString() + ")args[" + i.ToString() + "]";
}
src +=
")" + newLine +
"{" + newLine +
"return new " + item.ClassName + "(" + argsList + ");" + newLine +
"}" + newLine;
}
}

src +=
"}" + newLine;
}

src +=
"return null;" + newLine;
src +=
"}" + newLine +
"}" + newLine;
}


src +=
"}" + newLine;

Debug.WriteLine(src); // DO NOT DELETE THIS LINE - it prints the actual Class Factory to the Debug output

// Compile the actual implementation of the class factory
//CompilerResults cr = ic.CompileAssemblyFromSource(cpar,src);
CompilerResults cr = cdp.CompileAssemblyFromSource(cpar, src);
foreach (CompilerError ce in cr.Errors)
{
string errMsg = "DynamicClassFactory Compiler Error (nr. " + ce.ErrorNumber.ToString() +
") on line " + ce.Line.ToString() + ": " + ce.ErrorText;
Debug.WriteLine(errMsg);
EventLogWrapper.WriteEntry(EventType.Warning, errMsg);
}

if (cr.Errors.Count == 0 && cr.CompiledAssembly != null)
{
Type ObjType = cr.CompiledAssembly.GetType("ClassFactoryImpl");
try
{
if (ObjType != null)
{
instance =
Activator.CreateInstance(ObjType) as IClassFactory;
}
}
catch (Exception ex)
{
logException(
"DynamicClassFactory exception when creating internal instance: ", ex);
}
}
}
}
catch (Exception e)
{
// If any exception occurred, send it to Debug and propagate it on - because the application
// won't be able to continue
logException("ClassFactory.init() generated an exception: ", e);
throw;
}

// If there is any error - throw an exception. The application won't be
// able to continue from here anyway...
if (instance == null)
{
throw new Exception("The dynamic ClassFactory could not be created.");
}
}

DynamicClassFactory - Implementation

(Continuing my previous post)
Following is the actual implementation:


using System;
using System.Collections;
using System.Collections.Specialized;
using System.CodeDom.Compiler;
using System.Configuration;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;
using Microsoft.CSharp;

namespace DynamicClassFactory
{
/// <summary>
/// A dynamic ClassFactory.
/// This class has one static method that is used to create a new instance of an object.
/// When the class is accessed for the first time (i.e. the static ctor is called),
/// it loads the definition of the ClassFactory (including the set of supported classes) from
/// the config file. This ClassFactory is compiled and loaded to memory. Subsequently, every
/// call to the CreateInstance method will effectively return a new object of the requested class,
/// with minimum performance overhead (because the instance is created by simply calling the "new"
/// operator and not by using the Activator).
/// It is internally implemented as a singleton in order to ensure that the ClassFactory is
/// created only once for each AppDomain.
/// In order to provide maximum efficiency, the instantiations are done by giving the index
/// of the item type requested. However, in order to provide simplicity as well, it is possible
/// to generate the instance of a type given its name (instead of index), with a small performance-penalty.
///
/// The ClassFactory has the possibility to add item types at run-time. Each such addition requires a
/// runtime re-compilation of the internal implementation. Therefore, when many types must be added at once,
/// it is better to use the Add(ICollection) method, which performs the compilation only once for the whole list.
/// </summary>
public class ClassFactory
{
// Reference to the actual instance
private static IClassFactory instance = null;
// List of items that can be instantiated via the factory.
// This list is kept in order to be able to recreate the factory on-the-fly
private static ArrayList items;
// Last automatically generated index of item
private static int lastAutoKey;
// Lookup-table to retrieve the index of an item given its name
private static HybridDictionary stringLookupTable;
// Hide the ctor to enforce the singleton
private ClassFactory() {}
// Static ctor - called only once for each AppDomain
// This method loads information from the config file and then calls the init() method
// The config file must include a DynamicClassFactoryItemList section with all types
// that could be instantiated by the ClassFactory.
static ClassFactory()
{
try
{
lastAutoKey = 1000000;
stringLookupTable =
new HybridDictionary();
// Load items from configuration
DynamicClassFactoryItem[] factoryItems = (DynamicClassFactoryItem[])ConfigurationManager.GetSection("DynamicClassFactoryItemList");
if (factoryItems == null)
{
throw new Exception("DynamicClassFactory could not read the configuration file.");
}

items =
new ArrayList(factoryItems);
foreach (DynamicClassFactoryItem item in items)
{
generateKey(item);
stringLookupTable[item.ClassName] = item.Key;
}

init();
}
catch (Exception ex)
{
logException(
"The DynamicClassFactory static ctor generated an exception: ", ex);
throw;
}
}

/// <summary>
/// Initializes the ClassFactory.
/// Creates the code for the ClassFactory and compiles it.
/// If for any reason the ClassFactory could not be properly created, an exception is fired.
/// </summary>
private static void init()
{
// See next post....
}

/// <summary>
/// Returns a new instance of the class whose key in the config file corresponds to the
/// id parameter.
/// </summary>
/// <param name="id">The key of the class to be created</param>
/// <returns>A new instance of the class, or null if there is no corresponding key in the ClassFactory</returns>
public static object CreateInstance(int id, params object[] args)
{
try
{
return instance.CreateInstance(id, args);
}
catch (Exception e)
{
logException(
"An Exception occurred in CreateInstance("+id.ToString() +"). ", e);
throw;
}
}

/// <summary>
/// Returns a new instance of the given type with the given arguments.
/// If the typeName is unknown - an exception is thrown.
/// </summary>
/// <param name="typeName">Name of the type to instantiate</param>
/// <param name="args">Arguments for the constructor</param>
/// <returns>A new instance of the class</returns>
public static object CreateInstance(string typeName, params object[] args)
{
try
{
int key = (int)stringLookupTable[typeName];
return instance.CreateInstance(key, args);
}
catch (Exception e)
{
logException(
"An Exception occurred in CreateInstance(" + typeName + "). ", e);
throw;
}
}

/// <summary>
/// Adds the given item to the list of item types generated by the Factory.
/// This method causes the internal implementation to be re-compiled before it returns.
/// If several types have to be added to the factory, it is recommended to use the Add(ICollection)
/// overload of this method.
/// </summary>
public static void Add(DynamicClassFactoryItem item)
{
try
{
lock (items.SyncRoot)
{
if (addItem(item))
{
init();
}
}
}
catch (Exception e)
{
logException(
"DynamicClassFactory failed adding item with key " + item.Key.ToString() + ". ", e);
}
}

/// <summary>
/// Adds the given list of items to the list of item types generated by the factory.
/// This method causes the internal implementation to be re-compiled only once for the whole
/// list before it returns.
/// </summary>
public static void Add(ICollection newItems)
{
try
{
bool itemAdded = false;

lock (items.SyncRoot)
{
foreach (DynamicClassFactoryItem item in newItems)
{
if (addItem(item))
{
itemAdded =
true;
}
}

if (itemAdded)
{
init();
}
}
}
catch (Exception e)
{
logException(
"DynamicClassFactory failed adding collection. ", e);
}
}

// Add the given item to the list of supported items. If the item's Key is negative, the item
// is automatically being assigned a new, unique Key, in the range > 1,000,000
private static bool addItem(DynamicClassFactoryItem item)
{
try
{
lock (items.SyncRoot)
{
generateKey(item);

if ((!items.Contains(item)) && (!stringLookupTable.Contains(item.ClassName)))
{
items.Add(item);
stringLookupTable[item.ClassName] = item.Key;
return true;
}
return false;
}
}
catch (Exception e)
{
logException(
"DynamicClassFactory failed adding item with key " + item.Key + ". ", e);
}
return false;
}

// Checks whether the given item has a legal Key (>= 0). If it doesn't,
// a new, unique Key is generated in the range > 1,000,000
private static void generateKey(DynamicClassFactoryItem item)
{
if (item.Key < 0)
{
item.Key =
Interlocked.Increment(ref lastAutoKey);
}
}

private static void logException(string message, Exception e)
{
StringBuilder sb = new StringBuilder(1000);
sb.AppendFormat(
"{0}. Exception Message: {1}, Stack Trace: {2}.{3}", message, e.Message, e.StackTrace, Environment.NewLine);

Exception current = e.InnerException;
while (current != null)
{
sb.AppendFormat(
"Inner Exception Message: {0}.{1}", current.Message, Environment.NewLine);
current = current.InnerException;
}

string toLog = sb.ToString();
Debug.Write(toLog);
EventLogWrapper.WriteEntry(EventType.Error, toLog);
}
}

// Wrapper to the Windows Event Log.
internal class EventLogWrapper
{
private static readonly EventLogWrapper instance = new EventLogWrapper();
private EventLog eLog;
private EventLogWrapper()
{
if (!EventLog.SourceExists("DCF"))
{
EventLog.CreateEventSource("DCF", "");
}

eLog =
new EventLog();
eLog.Log =
"";
eLog.Source =
"DCF";
}
public static void WriteEntry(EventType eType, string message, params object[] args)
{
if (args.Length > 0)
{
message =
string.Format(message, args);
}

instance.eLog.WriteEntry(message, (
EventLogEntryType)eType);
}
}

internal enum EventType
{
Error =
EventLogEntryType.Error,
Warning =
EventLogEntryType.Warning,
Information =
EventLogEntryType.Information
}
}

DynamicClassFactory - Motivation

We are all familiar with Activator.CreateInstance. It's a great tool, which provides a simple way of dynamically instantiating an object that was unknown at design-time. An obvious us for is to support plug-ins in an application.
Yet Activator.CreateInstance has one huge drawback - it's sloooooooow! Performance has been significantly improved between .NET 1.1 and .NET 2.0, but still, it's on the order of 60 times slower than a simple call to "new" for a parameter-less constructor and up to 1000 times slower with a highly parameterized constructor. If you're instantiating an object once in a while, this has no meaning. Yet, if you need to instantiate a lot of such dynamically-defined objects, you need a better solution.

The DynamicClassFactory (DCF) comes to solve this exact problem. I have written it about 2 years ago, and intended to publish it on CodeProject ever since. Yet, I never find the time to do it. So I'll publish it here, on my blog, with the hope of one day writing a more elaborated article and publish it somewhere.

It's not perfect, and some parts need to be arranged, but that's what I'm currently using. I'd like to change the configuration part, and cleaning up some of the code (remove the id-based instantiation for example), but still - it's useful as it is.

The basic idea is as follows: every class you want to be supported by the DCF needs to be defined in the configuration file, including the paths where referenced libraries can be found. At run-time, the DCF reads all the constructors of these classes using Reflection and builds a helper class that knows how create the object with each of it constructors. All this is kept in a dynamically generated class, which given the parameters knows which helper class to call. You end up with a reference to this dynamically generated class, and creating an instance of an object becomes a simple (small) set of if-conditions and one "new" instantiation. The result is a performance of only 10 times slower than direct "new", regardless of the number of constructor parameters.

There will now be a set of 4 posts (including this one), which provide the complete code for the DCF. I don't have time to elaborate much - hope the code and comment suffice. Please let me know if you need any help...

Now let's begin.

First, let's look at the interface implemented by the dynamically generated class:


using System;

namespace DynamicClassFactory
{
public interface IClassFactory
{
object CreateInstance(int id, params object[] args);
}
}

Simple, right? This means that creating an object would be exactly the same as with Activator.CreateInstance.


To be continued in next post...

An interesting Neural Network library in C#

I've always been fascinated by Neural Networks - the basic principles are simple and more importantly - intuitive. Yet the final result is complex and rarely explanable. For some time, Neural Networks have been the best learning method for complex, highly non-linear problems. Nowadays it is not so, there are many more learning methods, be it kernel-based Support Vector Machines, Bayesian methods and others, each better at solving different problems. Despite my subjective liking for Neural Networks, I must admit that I never found any problem that was better solved with Neural Networks than with, for example, Support Vector Machines. Yet, there are tons of problems out there and almost as much different types of Neural Networks. So in many cases it's worth considering Neural Networks as one of the various problem solving options.

Andrew Kirrilov has published an interesting Neural Network library on CodeProject, which includes some important network architectures (Back Propagation, Kohonen Self-Organizing Map, Elastic Network, Delta Rule Learning, Perceptron Learning). It seems to me like a good way to better understand how Neural Networks work and maybe integrate them in some applications that require the solving of complex problems. Check it out!

P.S: I have never heard of a similar library for Support Vector Machines. Maybe when I have some time, I will implement something myself and publish it. I don't promise anything - time is currently the resource I lack the most...

Friday, November 17, 2006

The Irreplacable Myth

Too many companies have this Irreplacable Guy, let's call him Guru. He's been in the company for ages, knows all its history better than anyone else (politically as well as technically). Well, he should, for he's written some "core" components a decade ago and it's so damn smart that nobody has ever been able to understand it apart him. Well, nobody dares getting even close to it. Or just look at it. Even from very far away. When a problem occurs, people will do anything to avoid bothering Guru - he's so busy doing important stuff that they try to avoid disturbing him unless absolutely necessary. Even then, they put gloves on. Really thick ones. And Guru makes a lot of money - more than some of the VP's. Well he should - the company would collapse were he to squint at the windows of the company accross the street, even by accident. And Guru knows how important he is, and particularly - how irreplacable he is. The truth is, that deep inside, Guru is in tremendous stress - he knows that he's mostly bluff, and that all his "core knowledge" could be learned (with or without his help) in a few days. He even realizes that his skills have deteriorated over the years, for he never learned anything new in the last decade, nor done any serious work outside of his company. But he has no choice but to remain where he is, making sure the Irreplacable Myth remains as strong as ever and his job remains as secure as the Myth is strong.

In Hebrew we say: Graveyards are packed with irreplacable people.

Well it's true, in almost all cases, the Irreplacable Myth is exactly that - a Myth. It has been feeded by Guru himself, as well as the constantly changing staff, bad management and chance. I have personally been in both positions - an Irreplacable Guy and a Myth-Breaker. When I thought I was Irreplacable I used to joke that if I were to get run over by a truck, the company will crash. At some point I decided to leave the company - I hadn't been around enough time to become a real Myth. Yet my feeling of responsibility was so great that I told the company 3 months ahead of time that I was leaving (instead of the 1 month I was obliged by my contract). It took them a lot of time to find someone, so I had only a couple of weeks to pass everything to the new guy. And you know what? Everything went fine, the company didn't loose too much money and is still around today, making much more money than they did back then. On the other hand, I once started working for a company where there were not one, but two Irreplacable Guys. For various reasons (I must admit much of it had to do with me) they were both fired when the company had to cut down jobs, and not only didn't the company loose anything from it - many things went much better and faster, with a much better atmosphere after they left.

Are there real Myths? Well I think there are, but these are usually people not considered Irreplacable, so they become Myths only after they have left. These are usually very talented and creative people, who come up with ideas others would never be able to come up with. A real Myth is not one who knows a lot of stuff, he's one who creates things others wouldn't have thought of. That, ladies and gentlemen, is potentially an Irreplacable Guy!

Monday, November 13, 2006

A Prioritized Queue in C# - Enumerator and Synchronization classes

Continuing my previous post...
        [Serializable]
private class PrioritizedQueueEnumerator : IEnumerator, ICloneable
{
private PrioritizedQueue prioritizedQueue;
private int index;
private int version;
private int currentQueueIndex;
private IEnumerator currentEnumerator;
private int count;

internal PrioritizedQueueEnumerator(PrioritizedQueue prioritizedQueue)
{
this.prioritizedQueue = prioritizedQueue;
this.index = -1;
this.version = prioritizedQueue.version;
this.currentQueueIndex = -1;
this.count = prioritizedQueue.Count;
updateInternalEnumerator();
}

public bool MoveNext()
{
if (version != prioritizedQueue.version)
{
throw new InvalidOperationException("Enumerator was changed!");
}

index++;
if (index > count)
{
return false;
}
else
{
if (!currentEnumerator.MoveNext())
{
updateInternalEnumerator();
if (!currentEnumerator.MoveNext())
{
return false;
}
}
return true;
}
}

public void Reset()
{
index = -1;
}

public object Current
{
get
{
if (version != prioritizedQueue.version)
{
throw new InvalidOperationException("Enumerator was changed!");
}

if ((index < 0) || (index >= count))
{
throw new InvalidOperationException("Enumerator out of bounds");
}

Debug.Assert(currentEnumerator != null);

return currentEnumerator.Current;
}
}

public object Clone()
{
return base.MemberwiseClone();
}

private void updateInternalEnumerator()
{
currentQueueIndex++;
for (; currentQueueIndex < prioritizedQueue.queues.Length; currentQueueIndex++)
{
Queue queue = prioritizedQueue.queues[currentQueueIndex];
if (queue.Count > 0)
{
currentEnumerator = queue.GetEnumerator();
break;
}
}
}
}

[
Serializable]
private class SynchronizedPrioritizedQueue : PrioritizedQueue
{
private PrioritizedQueue prioritizedQueue;
private object syncRoot;

public override object SyncRoot
{
get { return syncRoot; }
}

public override int Count
{
get
{
lock (syncRoot)
{
return prioritizedQueue.Count;
}
}
}

public override bool IsSynchronized
{
get
{
return true;
}
}


internal SynchronizedPrioritizedQueue(PrioritizedQueue prioritizedQueue)
{
syncRoot =
new object();
this.prioritizedQueue = prioritizedQueue;
}

public override object Clone()
{
object obj = null;
lock (syncRoot)
{
obj =
new PrioritizedQueue.SynchronizedPrioritizedQueue((PrioritizedQueue)this.prioritizedQueue.Clone());
}
return obj;
}

public override void Clear()
{
lock (syncRoot)
{
prioritizedQueue.Clear();
}
}

public override bool Contains(object obj)
{
bool contains;
lock (syncRoot)
{
contains = prioritizedQueue.Contains (obj);
}
return contains;
}

public override object Dequeue()
{
object obj;
lock (syncRoot)
{
obj = prioritizedQueue.Dequeue();
}
return obj;
}

public override object Dequeue(QueuePriority priority)
{
object obj;
lock (syncRoot)
{
obj = prioritizedQueue.Dequeue (priority);
}
return obj;
}

public override void Enqueue(object obj)
{
lock (syncRoot)
{
prioritizedQueue.Enqueue (obj);
}
}

public override void Enqueue(object obj, QueuePriority priority)
{
lock (syncRoot)
{
prioritizedQueue.Enqueue (obj, priority);
}
}

public override object Peek()
{
object obj;
lock (syncRoot)
{
obj = prioritizedQueue.Peek ();
}
return obj;
}

public override object Peek(QueuePriority priority)
{
object obj;
lock (syncRoot)
{
obj = prioritizedQueue.Peek (priority);
}
return obj;
}

public override object[] ToArray()
{
object[] array;
lock (syncRoot)
{
array = prioritizedQueue.ToArray ();
}
return array;
}

public override void TrimToSize()
{
lock (syncRoot)
{
prioritizedQueue.TrimToSize ();
}
}

public override void CopyTo(Array array, int index)
{
lock (syncRoot)
{
prioritizedQueue.CopyTo (array, index);
}
}

public override IEnumerator GetEnumerator()
{
IEnumerator enumerator;
lock (syncRoot)
{
enumerator = prioritizedQueue.GetEnumerator ();
}
return enumerator;
}

}
}