Nigel Morse

  • Anonymous delegates rock

    Posted Tuesday, March 18, 2008 6:33 PM | 1 Comments

    I'm finding I really like C#'s anonymous delegates. I've used them several times now in various ways. Normal delegates are already a Good Thing, however basically they are C(++) function pointers with a much nicer syntax. Anonymous delegates do clever things like having the ability to access local variables.  This just provides some syntactic sugar and the compiler under the hood creates classes to hold the variables and puts the anonymous method on that class.  Take a look with a decompiler and you can see what gets generated.

    One "obvious" pattern I've seen people here come up with independently is to invoke something on a worker thread from a UI thread, or the other way round (and sometimes both by nesting one delegate inside another).

    However another use I like is what I want to share today.

    I've been using WMI a bit recently. WMI can be used (amongst other things) to get information about remote computers.  I won't go into the detail here - there's plenty on the web about it (http://en.wikipedia.org/wiki/Windows_Management_Instrumentation).  I may even do a blog myself on it one day.

    To access WMI via .NET you use the System.Management namespace.  There are various ways to call and get the information, but a common example is to run a query in a SQL-esque way - e.g. "select TotalPhysicalMemory  from Win32_ComputerSystem" will get you the total physical memory on a machine.  However to run a query like this requires quite verbose code, not least because a lot of objects you create need Dispose()ing of afterwards.


    ManagementScope scope 
    = new ManagementScope"\\\\mymachinename\\root\\cimv2" );
    scope.Connect();
    objQry = new ObjectQuery"select TotalPhysicalMemory from Win32_ComputerSystem" );
    UInt64 totalMemory 0;
    usingManagementObjectSearcher searcher = new ManagementObjectSearcherscope
    {
        
    usingManagementObjectCollection collection searcher.Get() )
        
    {
            
    foreachManagementObject obj in collection )
            
    {
                totalMemory 
    (UInt64)obj["TotalMemory"];
                
    obj.Dispose();
            
    }
        }
    }

    So great - i've got that out, but I want to find out how much free disk it has as well.  This query is "select Name,FreeSpace from Win32_LogicalDisk where DriveType=3" and could well return me more than one disk. (DriveType=3 means physical disks on this machine as opposed to mapped network drives etc.)

    I can reuse the scope so the extra code is...

    objQry = new ObjectQuery"select Name,FreeSpace from Win32_LogicalDisk where DriveType=3" );
    Dictionary<string,UInt64drives = new Dictionary<string,UInt64>();
    usingManagementObjectSearcher searcher = new ManagementObjectSearcherscope
    {
        
    usingManagementObjectCollection collection searcher.Get() )
        
    {
            
    foreachManagementObject obj in collection )
            
    {
                drives.add
    ( (string)obj["Name"], (UInt64)obj["FreeSpace"] );
                
    obj.Dispose();
            
    }
        }
    }

    But that instantly offends me - I have 13 lines of code, of which 10 are the same as above.  That's 10 lines repeated whenever I want to run another query.

    Instead I can define a method like this:

    private delegate void UseManagementObjectManagementObject obj );

    private static void DoWmiQueryManagementScope scopestring qryUseManagementObject callback )
    {
        ObjectQuery objQry 
    = new ObjectQueryqry );            
        
    usingManagementObjectSearcher searcher = new ManagementObjectSearcherscopeobjQry ) )
        
    {
            
    usingManagementObjectCollection collection searcher.Get() )
            
    {
                
    foreachManagementObject obj in collection )
                
    {
                    callback
    obj );
                    
    obj.Dispose();
                
    }
            }
        }
    }


    Now I can reduce my 2 previous pieces to this:

    ManagementScope scope = new ManagementScope"\\\\mymachinename\\root\\cimv2" );
    scope.Connect();
    UInt64 totalMemory 0;
    DoWMiQueryscope"select TotalPhysicalMemory from Win32_ComputerSystem"delegateManagementObject obj )
                       
    {
                           totalMemory 
    (UInt64)obj["TotalMemory"];
                       
    );

    Dictionary<string,UInt64drives = new Dictionary<string,UInt64>();
    DoWMiQueryscope"select Name,FreeSpace from Win32_LogicalDisk where DriveType=3"delegateManagementObject obj )
                       
    {
                           drives.add
    ( (string)obj["Name"], (UInt64)obj["FreeSpace"] );
                       
    );

    Jobs a good'un :)

  • Do you yield?

    Posted Wednesday, October 31, 2007 3:30 PM | 1 Comments

    Yay! I’ve actually used the C# yield statement for the first time. I’ve wanted to use it ever since I read about it, but never really had a use for it until today.

    I was writing some code to read a SQL Server error log and eventually found an undocumented stored procedure sp_readerrorlog (amazing how so much useful stuff for SQL Server seems to be “undocumented”).

    This procedure works on 2000 and 2005 and returns the entire current error log. However, (probably partly due to its undocumented nature) the different versions return the results in a different format. I need to look for things in the log so my first thought was to create a Parse2000Log and a Parse2005Log and then have these call a 3rd method (e.g. ParseLogLine) for each line. The problem with this was that the messages can span multiple lines. For example if you do a

    RAISERROR'foo'20WITH LOG

    the log shows.

    2007-10-30 15:11:36.07 spid52  Error: 50000, Severity: 20, State: 1
    2007-10-30 15:11:36.07 spid52  foo.
     

    This would mean I need to keep state in my ParseLogLine function to track which line I expect next, which means I’d need to make some class variables which I’d really rather avoid.   I’m more in favour of using local stack variables when I can.   Obviously there are numerous ways to skin this (as with most) cats, but then I hit on the idea of using yield to provide an enumerator over the results and hand back a struct for each line to a calling function.   This means I can separate out the differences between the servers into 2 functions but call them from a common function that can act on the actual message.

    The 2005 function is by far the simpler as it 2005 has pre-parsed the log into separate columns:

          // Struct to hold a line of the logfile
           
    private struct LogLine
            {
                public DateTime date;
                public string processInfo;
                public string message;
            }
            private static IEnumerable <LogLine> Parse2005Log  SqlDataReader reader )
            {
                LogLine ret;
                while( reader.Read() )
                {
                    ret.date = reader.GetDateTime( 0 );
                    ret.processInfo = reader.GetString( 1 );
                    ret.message = reader.IsDBNull( 2 ) ?
                                    String.Empty : reader.GetString( 2 );
                    yield return ret;
                }
     

    And there is the yield in action. What actually happens is that the compiler generates a class for you which (in this case) inherits from IEnumerable<T> andIEnumerator<T> and has the methods necessary. This means you only have to write one method and not the whole class which I think makes for neat solution in this case.   You can make the method return IEnumerable<T> orIEnumerator<T> (or the non-generic variants if you really want) and it will work it out.   Here I chose IEnumerable<T> so I can use it in a foreach statement

            IEnumerable<LogLine> logreader;
            if( s_is2005Server )
                logreader = Parse2005Log( reader );
            else
                logreader = Parse2000Log( reader );
            foreach( LogLine line in logreader )
            {
                Console.WriteLine( "{0} : {1} : {2}", line.date,
                                    line.processInfo, line.message );
            }
     
     
    Here’s the full example:              

    private struct LogLine
            {
                public DateTime date;
                public string processInfo;
                public string message;
            }
            static void Main(string[] args)
            {
                SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
                builder.DataSource = s_machineName;
                builder.IntegratedSecurity = true;
                string connStr = builder.ConnectionString;
     
                try
                {
                    using (SqlConnection conn = new SqlConnection(connStr))
                    {
                        conn.Open();
                        using (SqlCommand comm = conn.CreateCommand())
                        {
                            comm.CommandType = CommandType.StoredProcedure;
                            comm.CommandText = "sp_readerrorlog";
                            SqlDataReader reader = comm.ExecuteReader();
                            IEnumerable<LogLine> logreader;
                            if (s_is2005Server)
                                logreader = Parse2005Log(reader);
                            else
                                logreader = Parse2000Log(reader);
                            foreach (LogLine line in logreader)
                            {
                                Console.WriteLine("{0} : {1} : {2}", line.date,
                                              line.processInfo, line.message);
                            }
                        }
                    }
                }
                catch (SqlException ex)
                {
                    Console.WriteLine("Exceptional! - " + ex.Message);
                }
            }
            private static IEnumerable<LogLine> Parse2000Log(SqlDataReader reader)
            {
                LogLine ret;
                while (reader.Read())
                {
                    if (!reader.IsDBNull(0))
                    {
                        string line = reader.GetString(0);
     
                        // The format of the SQL 2000 log file is fairly
                        // contstrained to be a fixed length
                        // date, then fixed length process info field and then
                        // the message.
                        //
                        // .. or it can just be freeform


                        string maybedate = line.Substring(0, 22);
                        DateTime date;
                        if (DateTime.TryParse(maybedate,
                                               CultureInfo.InvariantCulture,
                                               DateTimeStyles.AssumeLocal,
                                               out date)
                                   && !line.StartsWith("\t"))
                        {
                            ret.date = date;
                            ret.processInfo = line.Substring(23, 7).TrimEnd();
                            ret.message = line.Substring(33).TrimEnd();
                        }
                        else
                        {
                            ret.date = DateTime.MinValue;
                            ret.processInfo = string.Empty;
                            ret.message = line;
                        }
                        yield return ret;
                    }
                }
            }
            private static IEnumerable<LogLine> Parse2005Log(SqlDataReader reader)
            {
                LogLine ret;
                while (reader.Read())
                {
                    ret.date = reader.GetDateTime(0);
                    ret.processInfo = reader.GetString(1);
                    ret.message = reader.IsDBNull(2)
                                 ? String.Empty : reader.GetString(2);
                    yield return ret;
                }
            }


















<September 2008>
SuMoTuWeThFrSa
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
Creating Technical Presentations
 Making a technical presentation is like being interviewed. It is not a skill that you are likely to... Read more...

Go With the Flow
 Knowing enough about the routes that messages take is vital to being an effective Exchange admin,... Read more...

Policy-Based Management
 Every DBA knows the frustration of trying to manage tens of servers, each of which has a subtly... Read more...

When Email Collaboration Could Have Changed History
 In our mission to make history relevant to the busy IT executive, we speculate how Email might have... Read more...

Bunnikins!
 When an IT manager is selected as a victim of office politics of a large corporate, it is time for him... Read more...