Wednesday, May 16, 2012

Singletons: C++ to C# “Main” Primer And Modular Program Design

Magic initialization of static classes, lets prime the singleton paradigm of C++ into C#. This is not as simple as the “static” keyword added to object classes, like in C++. There are different techniques, and I used an attribute and code that searches for that attribute. I can relax dependencies and lists of hard-coded initializations into modular design by reflection.

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          Parse      = (args) => {} ;
        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 types =
                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." ) ;
                    }
                 }
            }
        }
    }