June, 2007

Posts
  • Eric Gunnerson's Compendium

    App Domains and dynamic loading (the lost columns)

    • 3 Comments

    As promised, I'm going to start republishing some of my columns that were eaten by MSDN.

     

    I spent some time reading this one and deciding whether I would re-write it so that it was, like, correct. But it became clear that I didn't have a lot of enthusiasm towards that, so I've decided to post it as is (literally, as-is, with some ugly formatting because of how I used to do them in MSDN).

     

    I also am not posting the source, though I might be tempted to put it someplace if there is a big desire for it.

     

    So, on to the caveats...

     

    The big caveat is that my understanding of how the probing rules work was incorrect. To get things to work the way I have them architected, you need to put them somewhere in the directory tree underneath where the exe lives, and if they aren't in the same directory, you need to add the directory where they live to the private bin path. I may also have the shadow directory stuff messed up.

     

    So, without further ado, here's something that I wrote five years ago and has not been supplanted by more timely and more correct docs. AFAIK. If you know better references, *please* add them to the comments, and also comment on anything else that's wrong.

     

    App Domains and dynamic loading

     

    Eric Gunnerson
    Microsoft Corporation

    May 17, 2002

    Download the ???.exe sample file.
    ???
    MSDNSamples\C#

    Download or browse the ???.exe in the MSDN Online Code Center!href(/code/default.asp?URL=/code/sample.asp?url=/msdn-files/026/002/???/msdncompositedoc.xml).

    This month, I’m sitting in a departure lounge at Palm Springs airport, waiting to fly back to Seattle after an ASP.NET conference.

    My original plan for this month – to the extent that I have a plan – was to do some work on the expression parsing part of the SuperGraph application. In the past few weeks, however, I’ve received several emails asking when I was going to get the loading and unloading of assemblies in app domains part done, so I’ve decided to focus on that instead.

    Application Architecture

    Before I get into code, I’d like to talk a bit about what I’m trying to do. As you probably remember, SuperGraph lets you choose from a list of functions. I’d like to be able to put “add-in” assemblies in a specific directory, have SuperGraph detect them, load them, and find any functions contained in them.

    Doing that by itself doesn’t require a separate AppDomain; Assembly.Load() usually works fine. The problem is when you want to provide a way for the user to update those assemblies when the program is running, which is really desirable if you’re writing something that runs on a server, and you don’t want to stop and start the server.

    To do this, we’ll load all add-in assemblies in a separate AppDomain. When a file is added or modified, we’ll unload that AppDomain, create a new one, and load the current files into it. Then things will be great.

    To make this a little clearer, I’ve created a diagram of a typical scenario:

     

     

     

    In this diagram, the Loader class creates a new AppDomain named Functions. Once the AppDomain is created, Loader creates an instance of RemoteLoader inside that new AppDomain.

    To load an assembly, a load function is called on the RemoteLoader. It opens up the new assembly, finds all the functions in it, packages them up into a FunctionList object, and then returns that object to the Loader. The Function objects in this FunctionList can then be used from the Graph function.

    Creating an AppDomain

    The first task is to create an AppDomain. To create it in the proper manner, we’ll need to pass it an AppDomainSetup object. The docs on this are useful enough once you understand how everything works, but aren’t much help if you’re trying to understand how things work. When a Google search on the subject returned up last month’s column as one of the higher matches, I suspected I might be in for a bit of trouble.

    The basic problem has to do with how assemblies are loaded in the runtime. By default, the runtime will look either in the global assembly cache or in the currently application directory tree. We’d like to load our add-in applications from a totally different directory.

    When you look at the docs for AppDomainSetup, you’ll find that you can set the ApplicationBase property to the directory to search for assemblies. Unfortunately, we also need to reference the original program directory, because that’s where the RemoteLoader class lives.

    The AppDomain writers understood this, so they’ve provided an additional location in which they’ll search for assemblies. We’ll use ApplicationBase to refer to our add-in directory, and then set PrivateBinPath to point to the main application directory.

    Here’s the code from the Loader class that does this:

    AppDomainSetup setup = new AppDomainSetup();

    setup.ApplicationBase = functionDirectory;

    setup.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;

    setup.ApplicationName = "Graph";

    appDomain = AppDomain.CreateDomain("Functions", null, setup);

     

    remoteLoader = (RemoteLoader) 

        appDomain.CreateInstanceFromAndUnwrap("SuperGraph.exe",

            "SuperGraphInterface.RemoteLoader");

    After the AppDomain is created, the CreateInstanceFromAndUnwrap() function is used to create an instance of the RemoteLoader class in the new app domain. Note that the filename of the assembly the class is in is required, and the full name of the class.

    When this call is executed, we get back an instance that looks just like a RemoteLoader. In fact, it’s actually a small proxy class that will forward any calls to the RemoteLoader instance in the other AppDomain. This is the same infrastructure that .NET remoting uses.

    Assembly Binding Log Viewer

    When you write code to do this, you’re going to make mistakes. The documentation provides little advice on how to debug your app, but if you know who to ask, they’ll tell you about the Assembly Binding Log Viewer (named fuslogvw.exe, because the loading subsystem is known as “fusion”). When you run the viewer, you can tell it to log failures, and then when you run your app and it has problems loading an assembly, you can refresh the viewer and get details on what’s going on.

    This is hugely useful to find out, for example, that Assembly.Load() doesn’t require “.dll” on the end of the filename. You can tell this in the log because it will tell you it tried to load “f.dll.dll”.

    Dynamically Loading Assemblies

    So, now that we’ve gotten the application domain created, it’s time to figure out how to load an assembly, and extract the functions from it. This requires code in two separate areas. The first finds the files in a directory, and loads each of them:

     

    void LoadUserAssemblies()

    {

        availableFunctions = new FunctionList();

        LoadBuiltInFunctions();

     

        DirectoryInfo d = new DirectoryInfo(functionAssemblyDirectory);

        foreach (FileInfo file in d.GetFiles("*.dll"))

        {

            string filename = file.Name.Replace(file.Extension, "");

            FunctionList functionList = loader.LoadAssembly(filename);

     

            availableFunctions.Merge(functionList);

        }

    }

    This function in the Graph class finds all dll files in the add-in directory, removes the extension from them, and then tells the loader to load them. The returned list of functions is merged into the current list of functions.

    The second bit of code is in the RemoteLoader class, to actually load the assembly and find the functions:

    public FunctionList LoadAssembly(string filename)

    {

        FunctionList functionList = new FunctionList();

        Assembly assembly = AppDomain.CurrentDomain.Load(filename);

     

        foreach (Type t in assembly.GetTypes())

        {

            functionList.AddAllFromType(t);

        }   

        return functionList;

    }

    This code simple calls Assembly.Load() on the filename (assembly name, really) passed in, and then loads all the useful functions into a FunctionList instance to return to the caller.

    At this point, the application can start up, load in the add-in assemblies, and the user can refer to them.

    Reloading Assemblies

    The next task is to be able to reload these assemblies on demand. Eventually, we’ll want to be able to do this automatically, but for testing purposes, I added a Reload button to the form that will cause the assemblies to be reloaded. The handler for this button simply calls Graph.Reload(), which needs to perform the following actions:

    1.      Unload the app domain

    2.      Create a new app domain

    3.      Reload the assemblies in the new app domain

    4.      Hook up the graph lines to the newly created app domain

    Step 4 is needed because the GraphLine objects contain Function objects that came from the old app domain. After that app domain is unloaded, the function objects can’t be used any longer.

    To fix this, HookupFunctions() modifies the GraphLine objects so that they point to the correct functions from the current app domain.

    Here’s the code:

    loader.Unload();

    loader = new Loader(functionAssemblyDirectory);

    LoadUserAssemblies();

    HookupFunctions();

    reloadCount++;

     

    if (this.ReloadCountChanged != null)

        ReloadCountChanged(this, new ReloadEventArgs(reloadCount));

    The last two lines fire an event whenever a reload operation is performed. This is used to update a reload counter on the form.

    Detecting new assemblies

    The next step is to be able detect new or modified assemblies that show up in the add-in directory. The frameworks provide the FileSystemWatcher class to do this.  Here’s the code I added to the Graph class constructor:

    watcher = new FileSystemWatcher(functionAssemblyDirectory, "*.dll");

    watcher.EnableRaisingEvents = true;

    watcher.Changed += new FileSystemEventHandler(FunctionFileChanged);

    watcher.Created += new FileSystemEventHandler(FunctionFileChanged);

    watcher.Deleted += new FileSystemEventHandler(FunctionFileChanged);

    When the FileSystemWatcher class is created, we tell it what directory to look in and what files to track. The EnableRaisingEvents property says whether we want it to send events when it detects changes, and the last 3 lines hook up the events to a function in our class. The function merely calls Reload() to reload the assemblies.

    There is some inefficiency in this approach. When an assembly is updated, we have to unload the assembly to be able to load a new version, but that isn’t required when a file is added or deleted. In this case, the overhead of doing this for all changes isn’t very high, and it makes the code simpler.

    After this code is built, we run the application, and then try copying a new assembly to the add-in directory. Just as we had hoped, we get a file changed event, and when the reload is done, the new functions are now available.

    Unfortunately, when we try to update an existing assembly, we run into a problem. The runtime has locked the file, which means we can’t copy the new assembly into the add-in directory, and we get an error.

    The designers of the AppDomain class knew this was a problem, so they provided a nice way to deal with it. When the ShadowCopyFiles property is set to “true” (the string “true”, not the boolean constant true. Don’t ask me why…), the runtime will copy the assembly to a cache directory, and then open that one. That leaves the original file unlocked, and gives us the ability to update an assembly that’s in use. ASP.NET uses this facility.

    To enable this feature, I added the following line to the constructor for the Loader class:

    setup.ShadowCopyFiles = "true";

    I then rebuilt the application, and got the same error. I looked at the docs for the ShadowCopyDirectories property, which clearly state that all directories specified by PrivateBinPath, including the directory specified by ApplicationBase, are shadow copied if this property isn’t set. Remember how I said the docs weren’t very good in this area…

    The docs for this property are just plain wrong. I haven’t verified what the exact behavior is, but I can tell you that the files in the ApplicationBase directory are not shadow copied by default. Explicitly specifying the directory fixes the problem:

    setup.ShadowCopyDirectories = functionDirectory;

    Figuring that out took me at least half an hour.

    We can now update an existing file and have it correctly loaded in. Once I got this working, I ran into one more tiny problem. When we ran the reload function from the button on the form, the reload always happened on the same thread as the drawing, which means we were never trying to draw a line during the reload process.

    Now that we’ve switched to file change events, it’s now possible for the draw to happen after the app domain has been unloaded and before we’ve loaded the new one. If this happens, we’ll get an exception.

    This is a traditional multi-threaded programming issue, and is easily handled using the C# lock statement. I added a in the drawing function and in the reload function, and this ensures that they can’t both happen at the same time. This fixed the problem, and adding an updated version of an assembly will cause the program to automatically switch to a new version of the function. That’s pretty cool.

    There’s one other weird bit of behavior. It turns out that the Win32 functions that detect file changes are quite generous in the number of changes they send, so doing a single update of a file leads to five change events being sent, and the assemblies being reloaded five times. The fix is to make a smarter FileSystemWatcher that can group these together, but it’s not in this version.

    Drag and Drop

    Having to copy files to a directly wasn’t terribly convenient, so I decided to add drag and drop functionality to the app. The first step in doing this is setting the AllowDrop property of the form to true, which turns on the drag and drop support. Next, I hooked a routine to the DragEnter event. This is called when the cursor moves in an object on a drag and drop operation, and determines whether the current object is acceptable for drag and drop.

    private void Form1_DragEnter(

        object sender, System.Windows.Forms.DragEventArgs e)

    {

        object o = e.Data.GetData(DataFormats.FileDrop);

        if (o != null)

        {

            e.Effect = DragDropEffects.Copy;

        }

        string[] formats = e.Data.GetFormats();

    }

     In this handler, I check to see if there is FileDrop data available (ie a file is being dragged into the window). If this is true, I set the effect to Copy, which sets the cursor appropriately and causes the DragDrop event to be sent if the user releases the mouse button. The last line in the function is there purely for debugging, to see what information is available in the operation.

    The next task is to write the handler for the DragDrop event:

    private void Form1_DragDrop(

        object sender, System.Windows.Forms.DragEventArgs e)

    {

        string[] filenames = (string[]) e.Data.GetData(DataFormats.FileDrop);

        graph.CopyFiles(filenames);

    }

    This routine gets the data associated with this operation – an array of filenames – and passes it off to a graph function, which copies the files to the add-in directory, which will then cause the file change events to reload them.

    Status

    At this point, you can run the app, drag new assemblies onto it, and it will load them on the fly, and keep running. It’s pretty cool.

    Other Stuff

    C# Community Site

    I’ve set up a Visual C# Community Newsletter, so that the C# product team has a better way to communicate with our users. I’m going to use it to announce when there’s new content on our community site at http://www.gotdotnet.com/team/csharp!href(http://www.gotdotnet.com/team/csharp), and also to let you know if we’re going to be at a conference or user group meeting.

    You can sign up for it at the site listed above.

    C# Summer Camp

    This coming August, we're teaming up with developmentor to host C# Summer Camp. This is a chance to receive excellent C# training from developmentor instructors and to spend some time with the C# Product Team. There's more information at the developmentor site!href(http://www.developmentor.com/conferences/csharpsummer/csharpsummer.aspx).

    Next Month

    If I do more work on SuperGraph, I’ll probably work on a version of FileSystemWatcher that doesn’t send lots of extraneous events, and possibly on the expression evaluation. I also have another small sample that I may talk about instead.

    <HR NOSHADE SIZE=1>

    Eric Gunnerson is a Program Manager on the Visual C# team, a member of the C# design team, and the author of A Programmer's Introduction to C#!href(http://www1.fatbrain.com/asp/bookinfo/bookinfo.asp?theisbn=1893115860&vm=c). He's been programming for long enough that he knows what 8-inch diskettes are and could once mount tapes with one hand.

     

  • Eric Gunnerson's Compendium

    To m_ or no to m_, that is the question...

    • 44 Comments

    I apologize for shocking your system by posting more than once a month - there are reasons for that, but I unfortunately can't get into them right now - but Keith added an interesting comment to my last post. He said:

    Side Note: a bit disturbing you're using C++ naming conventions in C# though? :)  No doubting your a ninja coder and I love your stuff, but seriously, bringing the m_ prefixing into C# is a bit of a "cant teach an old dog new tricks" thing.  

    This is pretty close to a "religious question", but since it's my blog, I'm always right (as codified in the "decree on Gunnerson infallibility of 1997"), so I'll take it on.

    When I first started writing code, a lot of our samples were written without any way of indicating whether something was a field or not, and I wrote a considerable amount of code using that idiom. What I found was that when I went back and looked at my code later, I had to scroll around to find out where each variable came from, and that made understanding the code harder.

    I toyed for a while with using just and underscore "_name", but I didn't like that. A single underline is a bit hard to pick up visually, and it seemed like I was inventing a different expression just to be different. So, I switched back to "m_", and I must say that I'm happy with the choice. The only place I don't like it is with events or other public fields, which are then named differently, but I'm willing to deal with that.

    The only other place I use prefixes is on pointers, where I just use "p". Unsafe code is rare enough that I want to have an indicator of what's going on.

    [Update: Another reason to use m_ is to make it easier to find your variable names in intellisense when you're working with controls, since there are roughly 4000 members in the average control class. I've also been using "c_" in the names of controls for the same reason]

    So, what do you think, keeping in mind that if you disagree, you're wrong...

  • Eric Gunnerson's Compendium

    Lost Column #2: Unsafe Image Processing

    • 9 Comments

    (circa November 15, 2001) 

    I think this column stands up pretty well without caveat.

    I should note that I wrapped the image class to provide nicer pixel-based access in a class you can find here. I suggest basing your code on that rather than what I wrote in the column.

    I should also note that the grey-scale code isn't what you want. The human eye is not equally sensitive to all colors, so you should use the following to determine the intensity:

     0.299 * red + 0.587 * green + 0.114 * blue

     Finally, I'll note that the extreme speedup I get here is because of how the underlying unmanaged GDI+ code is structured.

    ******

    Unsafe Image Processing

    Last month, we talked a bit about what unsafe code was good for and worked through a few examples. If you looked at the associated source code, you may have noticed that there was an image-processing example. This month, we're going to work through that example.

    Grey Scale Images

    Last summer, I was writing a program to process some pictures from my digital camera and I needed to do some image processing in C#. My first task was to figure out how to do that using the .NET fFrameworks. I did a bit of exploration, and found the Image and Bitmap classes in the System.Drawing namespace. These classes are thin covers over the GDI+ classes that encapsulate the image functions.
    One task I wanted to do was to walk through an image and convert it from color to grayscale. To do this, I needed to modify each pixel in the bitmap. I started by writing a BitmapGrey class that would encapsulates the bitmap, and then writing a function in that class to do the image conversion. I came up with the following class:


    public class BitmapGrey
    {
        Bitmap bitmap;

        public BitmapGrey(Bitmap bitmap)
        {
            this.bitmap = bitmap;
        }

        public void Dispose()
        {
            bitmap.Dispose();
        }

        public Bitmap Bitmap
        {
            get
            {
                return(bitmap);
            }
        }

        public Point PixelSize
        {
            get
            {
                GraphicsUnit unit = GraphicsUnit.Pixel;
                RectangleF bounds = bitmap.GetBounds(ref unit);

                return new Point((int) bounds.Width,
                                 (int) bounds.Height);
            }
        }

        public void MakeGrey()
        {
            Point size = PixelSize;

            for (int x = 0; x < size.X; x++)
            {
                for (int y = 0; y < size.Y; y++)
                {
                    Color c = bitmap.GetPixel(x, y);
                    int value = (c.R + c.G + c.B) / 3;
                    bitmap.SetPixel(x, y,
                                    Color.FromArgb(value, value, value));
                }
            }
        }
    }

    The meat of the class is in the MakeGrey() method. It gets the bounds of the bitmap, and then walks through each pixel in the bitmap. For each pixel, it fetches the color and determines what the average brightness of that pixel should be. It then creates a new color value for the brightness value, and stores it back into the pixel.

    This code was simple to write, easy to understand, and worked the first time I wrote it. Unfortunately, it's pretty slow. ; iIt takes about 14 seconds to process a single image. That's pretty slow if I compare it to a commercial image processing program, such as Paint Shop Pro, which can do the same operation in under a second.

    Accessing the Bitmap Data Directly

    The majority of the processing time is being spent in the GetPixel() and SetPixel() functions, and to speed up the program, I needed a faster way to access the pixels in the image.  There's an interesting method in the Bitmap class called LockBits(), which can be used – not surprisingly – to lock a bitmap in memory so that it doesn't move around. With the location locked, it's safe to deal with the memory directly, rather than using the GetPixel() and SetPixel() functions. When LockBits() is called, it returns a BitmapData class. The scan0 field in the class is a pointer to the first line of bitmap data. We'll access this data to do our manipulation.

    First, however, we need to understand a bit more about the how the data in the image is arranged.

    Bitmap Data Organization

    The organization of the bitmap depends upon the type of data in the bitmap. By looking at the PixelFormat property in the BitmapData class, we can determine what data format is being used. In this case, I'm working with JPEG images, which use the Format24bppRgb (24 bits per pixel, red green blue) format.

    Since we're going to be looking at these pixels directly, we'll need a way to decode a pixel. We can do that with the PixelData struct:

    public struct PixelData
    {
        public byte blue;
        public byte green;
        public byte red;
    }

    Now, we'll need a way to figure out what the offset is for a given pixel. Basically, we treat the bitmap data as an array of PixelData structures, and figure out what index we'd be looking for to reference a pixel at x, y.

    In memory, a bitmap is stored as one large chunk of memory, much like an array of bytes.  We therefore need to figure out what the offset is of a given pixel from the beginning of the array. Here's what a 4x4  bitmap would look like, with the index and (y, x) location of each pixel.



    0: (0, 0) 1: (0, 1) 2: (0, 2) 3: (0, 3)
    4: (1, 0) 5: (1, 1) 6: (1, 2) 7: (1, 3)
    8: (2, 0) 9: (2, 1) 10: (2, 2) 11: (2, 3)
    12: (3, 0) 13: (3, 1) 14: (3, 2) 15: (3, 3)

    For each line, the offset is simply the width of the line in pixels times the y value. This gives the index of the first element of the line, and the x value is simply added to that value to determine the actual index of an element. This is the way that multi-dimensional arrays are typically stored in memory.

    So, we can figure out the offset as follows:

    Offset = x + y * width;

    The code to access a given pixel is something like:

    PixelData *pPixel;
    pPixel = pBase + x + y * width;

    Where pBase is the address of the first element.

    If we go off and write some code, we'll find that this works great for some bitmaps, but doesn't work at all for other bitmaps. It turns out that the number of bytes in each line must be a multiple of 4 bytes, and since the pixels themselves are 3 bytes, there are some situations where there's some unused space at the end of each line. If we use the simple version of indexing above, we'll sometimes index into the unused space.

    Here's an example of what this looks like in a bitmap, with each box now corresponding to a byte rather than a pixel.

    0,0
    blue
    green red 0,1
    blue
    green red 0,2
    blue
    green red      
    1,0
    blue
    green red 1,1
    blue
    green red 1,2
    blue
    green red      

    This bitmap occupies 24 bytes, with 12 bytes per line. However, it only has three entries, so each line must be padded with an extra 3 bytes.

    To make our code work everywhere, we need to switch from working in terms of pixels to working in terms of bytes, at least for dealing with the line indexing. We also need to calculate the width of a line in bytes. We can write a function that handles this all of this for us:

    public PixelData* PixelAt(int x, int y)
    {
        return (PixelData*) (pBase + y * width + x * sizeof(PixelData));
    }

    Once that we've gotten that written that bit of code, we can finally write an unsafe version of our grey scale function:
    public void MakeGreyUnsafe()
    {
        Point size = PixelSize;
        LockBitmap();

        for (int x = 0; x < size.X; x++)
        {
            for (int y = 0; y < size.Y; y++)
            {
                PixelData* pPixel = PixelAt(x, y);

                int value = (pPixel->red + pPixel->green + pPixel->blue) / 3;
                pPixel->red = (byte) value;
                pPixel->green = (byte) value;
                pPixel->blue = (byte) value;
            }
        }
        UnlockBitmap();
    }

    We start by calling a function to lock the bitmap. This function also figures out the width of the bitmap and stores the base address away. We then iterate through the pixel, pretty much just as before, except that for each pixel, we get a pointer to the pixel and then manipulate it through the pointer.

    When this is tested, it only takes about 1.2 seconds to do this image. That's over 10 times faster. It's not clear where all the overhead is in GetPixel() / SetPixel(), but there's obviously some overhead in the transition from managed to unmanaged code, and when you have to do the transition 3.3 million times per image, that overhead will adds up.

    I originally stopped with this version, but a bit of reflection suggested another opportunity for improvement. We're calling the PixelAt() function for every pixel, even when we know that the pixels along a line are contiguous.  We'll exploit that in the final version:

    public void MakeGreyUnsafeFaster()
    {
        Point size = PixelSize;
        LockBitmap();

        for (int y = 0; y < size.Y; y++)
        {
            PixelData* pPixel = PixelAt(0, y);
            for (int x = 0; x < size.X; x++)
            {
                byte value =
                    (byte) ((pPixel->red + pPixel->green + pPixel->blue) / 3);
                pPixel->red =  value;
                pPixel->green = value;
                pPixel->blue = value;
                pPixel++;
            }
        }
        UnlockBitmap();
    }

    The loop in this version does the y values in the outer loop, so we can walk through the entire x line. At the beginning of each line, we use PixelAt() to find the address of the first element, and then use pointer arithmetic (the pPixel++ statement) to advance to the next element in the line.

    My test bitmap is 1,536 pixels wide. With this modification, I'm replacing a call to PixelAt() with an additional operation in all but one of those pixels.

    When this version is tested, the elapsed time is 0.52 seconds, which is over twice as fast as our previous version, and nearly 28 times faster than the original version. Sometimes unsafe code can be really extremely useful, though gains of 28x are pretty rare.

  • Eric Gunnerson's Compendium

    The problem with intellisense

    • 4 Comments

    Today I was working with some sample code, and I came across a misspelling. Not a big deal - there was a field that was named "m_postion" rather than "m_position".

    But that got me thinking...

    In the past, that sort of thing wouldn't have happened. You would have written:

    int m_postion;

    but then, when you wrote your code, you would have written:

    m_position = 5;

    and the compiler would have complained. But with intellisense, you now just pick whatever looks right in the popup list, and the mistakes stay around a bit longer.

    So, I wrote a bit of code to help with this - it reflects over an assembly, and produces a list of words.

     

     

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Reflection;
    
    namespace IdentifierExtract
    {
        class GetIdentifiers
        {
            Dictionary m_identifiers = new Dictionary();
    
            public GetIdentifiers()
            {
            }
    
            public void Process(string assemblyFilename)
            {
                Assembly assembly = Assembly.LoadFrom(assemblyFilename);
    
                foreach (Type type in assembly.GetTypes())
                {
                    ProcessType(type);
                }
            }
    
            void ProcessType(Type type)
            {
                BreakIntoWordsAndAdd(type.Name);
    
                foreach (MemberInfo memberInfo in type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
                {
                    BreakIntoWordsAndAdd(memberInfo.Name);
    
                    MethodBase methodBase = memberInfo as MethodBase;
    
                    if (methodBase != null)
                    {
                        foreach (ParameterInfo parameterInfo in methodBase.GetParameters())
                        {
                            BreakIntoWordsAndAdd(parameterInfo.Name);
                        }
                    }
                }
            }
    
            void BreakIntoWordsAndAdd(string identifier)
            {
                List words = BreakPascalCasing(identifier);
    
                foreach (string word in words)
                {
                    AddIdentifer(word);
                }
            }
    
            List BreakPascalCasing(string identifier)
            {
                Regex regex = new Regex(".[a-z]+");
                
                MatchCollection matches = regex.Matches(identifier);
    
                List words = new List();
                foreach (Match match in matches)
                {
                    words.Add(match.Value);
                }
    
                return words;
            }
    
            private void AddIdentifer(string name)
            {
                name = name.ToLower();
                if (!m_identifiers.ContainsKey(name))
                {
                    m_identifiers.Add(name, null);
                }
            }
    
            public void WriteToConsole()
            {
                List identifiers = new List(m_identifiers.Keys);
                identifiers.Sort();
    
                foreach (string identifier in identifiers)
                {
                    Console.WriteLine(identifier);
                }
            }
        }
    }
    
  • Eric Gunnerson's Compendium

    Does YAGNI ever apply to tests?

    • 8 Comments

    I've been writing a small utility to help us do some configuration setup for testing. It needs to walk a directory structure, find all instances of a specific xml file, and then make some modifications to the file.

    I TDD'd the class that does the XML file stuff, and I'm confident that it's working well. I'm now going to do the class to walk of the directory structure and find the files.

    And there is my dilemna. I don't know if I'm going to do TDD on that.

    I know exactly how to write it, I've written it before, and my experience is that that is code that never changes nor breaks. And, figuring out how to write tests for it is going to be somewhat complex because I'll have to isolate out the live file system parts.

    So, I've already decided what I'm going to do, but I'm curious what you think. Does YAGNI apply to test code, or is that the first step to the dark side?

  • Eric Gunnerson's Compendium

    Wood 6-bit adder

    • 2 Comments
    Wood 6-bit adder
  • Eric Gunnerson's Compendium

    A crime against consumers...

    • 4 Comments

    I was reading a blog today, and came across an inline ad:

     

    Which got me to thinking... 

    When I was growing up, triscuits were not a popular snack food. My sisters and I were devotees of the

    So much so, that when we went on family trips, one of us would be appointed as the "Wheat Thin Sheriff" to arbitrate any disagreements over the apportionment of the aforementioned snack (you may be surprised at the lengths that we would go to, but you have likely never had to endure the unbridled excitement of a car trip from Seattle to Idaho in the heat of summer).

    There was usually a box of Triscuits along as well, which we were happy let remain in the front seat between our parents. Triscuits are, after all, just an attempt to take shredded wheat breakfast cereal and pass it off as a snack, and were markedly inferior to wheat thins.

    Not to mention the name, an obvious play on the word "biscuit" (that traditional American snack food), but what does the "tri" mean? And is there a "Tetrascuit" under lock and key in a top-secret Nabisco research lab?

    Anyway, for a long time, I actively avoided them.

    And then I got older, and found that a) Triscuits are okay to eat, but not a great snack and therefore 2) I can eat 4 or 5 and be done with them.  Which is a good thing when you want a good snack and don't want to eat a whole box of:

    Then one day while shopping, I noticed the Triscuit Rosemary and Olive oil flavor.

    I am generally skeptical of new flavors for old favorites, on the theory that the primary flavors of good snack foods are (in order) salt, oil, <some sort of starch>, but I bought a box anyway.

    Only to find that Nabisco had overdone my second criteria. Not only could I eat 4 or 5 and be satisfied, I could eat 4 or 5 and never want to eat any more, smell them, or even see the box. They were, to put it simply, a crime against consumers - something that managed to combine ingredients I liked to create a thoroughly unappetizing result.

    A couple of months later, I saw a familiar box sitting on the table next to the couch. I sat down, turned on the TV, reached into the box, and pulled out an "original flavor" (ie no flavor) Triscuit. Or so I thought in the dim illumination. It turns out that my lovely wife had not purchased original flavor triscuits, but had instead bought a different flavor that she thought was pretty good:

    I disagreed with her assessment. Chedar cheese triscuits are not "pretty good", they are the kind of food that should say "serving size: 1 box" on the side. If I ever die from nutritional insufficiency, you can be pretty sure that you will find me on the couch with the TV on and a laptop toasting my legs, surrounded by a pile of empty cheddar triscuit boxes.

    Foods like this should be illegal.

  • Eric Gunnerson's Compendium

    YAGNI and unit tests...

    • 7 Comments

    Thanks for your comments.

    I decided to go ahead and write the unit tests for that layer, both because I knew what not writing them would be like, and I wanted to play with wrapping/mocking a system service.

    I also decided - as some of you commented - to do the right thing and encapsulate it into a class. That would have happened long ago, but though I've written it several times, I don't think I've ever duplicated it within a single codebase - and the codebases where I did write it are pretty disparate. Now, I have something where I could at least move the source file around...

    Writing tests for this was a bit weird, because in some sense what I needed to do was figure out what the system behavior was, break that down, write a test against my objects, and then write mocks that allowed me to simulate the underlying behavior.

    So, for example, I created a test to enumerate a single file in a single directory, wrote a wrapper around DirectoryInfo, and then created a mock on that object so I could write GetFiles() to pass back what I wanted. And so on with multiple files, sub-directories, etc.

    So, I did that, went to write the little bit of code that I needed in the real version (to use the real GetFiles() calls and package the data up), hooked it up to my real code, and it worked.

    *But*, when I went back and looked at the code, I found that what I had really done was create two sets of code. There was the real code that called the system routines and shuffled the data into my wrapped classes. And then there was my mock code that let me control what files and directories got returned. But there wasn't any common code that was shared.

    So, my conclusion is that I really didn't get anything out of the tests I wrote, because the tests only tested the mocks that I wrote rather than the real code, because the only real code was the code that called the system functions.

    In this case, TDD didn't make sense, and I will probably pull those tests out of the system.TDD may make sense the next level up, where I've written a new encapsulation around directory traversal, but it seems like the only code there is hookup code.

    So, the result of my experiement was that, in this case, writing the tests was the wrong thing to do.

     

     

     

  • Eric Gunnerson's Compendium

    I apologize

    • 4 Comments
    Desktop Tower Defense
  • Eric Gunnerson's Compendium

    Some alternative development methodologies...

    • 1 Comments

    If scrum isn't to your liking, here are a few alternate methodologies that you might consider...

  • Eric Gunnerson's Compendium

    Cycling information...

    • 1 Comments

    For a while I've been toying with the idea of writing more about cycling than I have in the past - as I seem to have developed a passion for the sport - but too much of any one thing isn't good in a blog (as evidenced by my earnest desire to avoid to much useful C# stuff).

    So, I've had things I wanted to write about but didn't. A little time spent installing community server on my hosted site and the simple purchase of a domain name now lets me introduce my new cycling blog:

    RiderX

    If you also have that passion, please stop by.

    I will still reference major events here, whether they are cycling-related or not.

  • Eric Gunnerson's Compendium

    Friday fun

    • 1 Comments

     

    Man, but that's cool.

    CNN article

    Website with gallery

     

  • Eric Gunnerson's Compendium

    Robot Chicken Star Wars

    • 0 Comments

    Robot Chicken Star Wars

    From BA

     

  • Eric Gunnerson's Compendium

    Not sure what I think of this

    • 0 Comments

    What would Freddie think about this?

     You'll need to go to about :45 seconds in on the video...

  • Eric Gunnerson's Compendium

    Nice Ad for VS

    • 1 Comments

    Why debugging matters

     

Page 1 of 1 (15 items)