The key to modular design is that the code in one module does not bleed over into other modules. In samples of code, I found hard-coded dependencies typed in one module with code that has nothing to do with that module except the call itself. That seemed mundane when we can let System.Linq help resolve dependencies within assemblies of ambiguous modules. We can go back and hard-code them, unambiguously, yet with longer lists of dependencies then the harder the development becomes as more updates are prerequisites. Modular design lessens the impact of change-sets in stable code.
Note that in C#, the "static" keyword does not guarantee that the virtual machine loads and initializes all these classes on start-up; however, we can search the assembly manifests. As we lookup each class for attributes, then we can select which ones we initialize. The virtual machine then loads each class upon reference.
Instead of the C++'s magic initialization, I used the attribute “Program.Singleton” here. The example class, below, shows it sets the actions found in Program.Main().
using System ;
namespace Application
{
[Program.Singleton]
class Example
{
private Example()
{
Program.Parse += (args) => Console.WriteLine( "Parse({0})", args.ToString() ) ;
Program.Initialize += () => Console.WriteLine( "Initialize()" ) ;
Program.Run += () => Console.WriteLine( "Run()" ) ;
}
}
}
That example class works as one module. You can create Example1, Example2, and Example3 as copies of that example class, put them in separate files in your project (with the Program class below), and compile them together. They each then take their turn as they go through the instantiation, Parse(), Initialize(), and finally Run(). You need at least one Run() for the foreground loop. For something less generic, you can expand Run() into its iterative actions.
Related Posts: Part 1 2 3 4
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Collections ;
using System.Reflection ;
using System.Linq ;
using System ;
namespace Application
{
public sealed class Program
{
static public Action
static public Action Initialize = () => {} ;
static public Action Run = () => {} ;
static Dictionary< string, object > singletons = new Dictionary< string, object >() ;
[AttributeUsage(AttributeTargets.Class)]
public class SingletonAttribute : Attribute {}
static Program()
{
#if DEBUG
Debug.Listeners.Add( new TextWriterTraceListener( Console.Out ) ) ;
#else
AppDomain.CurrentDomain.UnhandledException += unhandled ;
#endif
}
public static void Main( string[] args )
{
instantiate() ;
Parse( args ) ;
Initialize() ;
Run() ;
#if DEBUG
singletons.Clear() ;
GC.Collect() ;
GC.WaitForPendingFinalizers() ;
Debug.WriteLine( "Program ended." ) ;
#endif
Environment.Exit( 0 ) ;
}
static void unhandled( object o, UnhandledExceptionEventArgs args )
{
Exception e = (Exception)args.ExceptionObject ;
Console.Write( "UNHANDLED EXCEPTION: " ) ;
Console.WriteLine( e ) ;
Environment.Exit( 1 ) ;
}
static void instantiate()
{
IEnumerable
from type in Assembly.GetExecutingAssembly().GetTypes()
from atrb in Attribute.GetCustomAttributes( type )
where atrb is SingletonAttribute
select type ;
foreach( Type t in types )
{
#if DEBUG
Debug.WriteLine( "Singleton: " + t.ToString() ) ;
#endif
try
{
object o = t.InvokeMember( null,
BindingFlags.DeclaredOnly
| BindingFlags.NonPublic
| BindingFlags.Instance
| BindingFlags.CreateInstance ,
null, null, null );
singletons[ t.ToString() ] = o ;
}
catch( System.MissingMethodException )
{
throw new MissingMethodException( "Class '" + t.ToString()
+ "' attributed for 'Program.Singleton' instantiation,"
+ " yet it lacks a private constructor." ) ;
}
}
}
}
}