Rainer's profileCyrons BlogPhotosBlogListsMore Tools Help

Blog


    October 10

    Updates für Musikstücke?

    Heute Vormittag hörte ich auf NDR2 eines meiner Lieblingsstücke, “Here I go again” von White Snake. Eines hat mich dabei stutzig gemacht: Es kam mir ausgefeilter vor als die “alte” Version die ich kenne. Toller Surround Sound und perfekte Gitarren-Riffs ergaben ein homogenes, harmonisches Ganzes und nicht, wie sonst üblich bei Rock-Musik, einen Lärmbrei aus konkurrierenden Instrumenten und einem Sänger der in dem Getöse fast untergeht.
    Ob White Snake den Song wirklich neu aufgelegt hat, oder was sonst der Grund für den Hörgenuss war, lasse ich mal dahin gestellt. Jedenfalls kam mir dabei die Idee von <SongXYZ> V2.0. Jaja, ich kann mein Entwicklerherz eben nicht leugnen. :-D
    Wie wäre es denn wenn Bands bzw. Produzenten für den einen oder anderen Song wirklich ein Update machen?
    Es ist doch viel besser, es kommt dabei so etwas tolles heraus wie ich heute Vormittag hörte, als daß irgend ein Plagiator das Stück als “verschlimmbesserte” Version auf den Markt bringt und damit auch noch dicke Kohle macht, nur weil er/sie einen großen Namen trägt und naive Fans eben alles kaufen.
    Mit einem Update dagegen könnten die wahren Urheber noch einmal abkassieren – und das sogar ohne ein schlechtes Gewissen haben zu müssen.

    DotNetKicks-DE Image
    October 05

    DotNet Expansions framework

    For several years of my developer career I gathered useful code into my own framework. Formerly known as “Cyrons Framework” the new name is “.Net Expansions”.

    The actual build 1.0.3554.36533 consists of the following:

    • DotNetExpansions Namespace
      • ExceptionEventArgs
      • Extension methods
        • Limit<T>
        • LimitRange<T>
        • TimeStamp
        • ExactTimeStamp
      • LimitEventArgs
      • StateManager // (Multiple Undo/Redo for everything!)
    • DotNetExpansion.Collections Namespace
      • CollectionContainer<T>
      • SharedCollectionContainer<T>
    • DotNetExpansions.CollectiveEvent Namespace
      • CollectiveEventContract // (many events, one or many listener(s))
      • CollectiveEventArgs
    • DotNetExpansions.Forms Namespace
      • ControlCommunicationsManager // (cross thread handling of Windows forms controls)
      • ControlExtensions (at the time being it consists of a cross thread control invoker developed by Marcell Spies)
      • Fading Message Box // (well known)
      • ProgressionStateInfo // (notifies a listener about the progression of a function)
    • DotNetExpansions.IO Namespace
      • CompressedBinaryFileIO // (easy saving and loading of any data in compressed binary format)
      • XmlIo // (easy saving and loading of any business objects to/from XML files)
    • DotNetExpansions.Logger Namespace
      • Logger // (base class for specific logging classes)
      • TextLogger
      • SqlLogger (not fully finished yet but usable)
      • more loggers to come…
    • DotNetExpansions.MathLib Namespace // (growing)
      • Mathematical and physics constants
      • mathematical conversions
      • Statistics
    • DotNetExpansions.MathLib.Geometry Namespace
      • // Too many to list here!
    • DotNetExpansions.MathLib.Physics Namespace
      • Electronics
      • Kinetics
    • DotNetExpansions.ParallelSearch Namespace
      • ParallelFileSearch
    • DotNetExpansions.Picture Namespace
      • PictureHandler // (easy handling of image IO in Windows forms)
    • DotNetExpansions.Time
      • Time // (easy handling for many aspects of time).

    The download package (Zip file or Visual Studio 2008 solution) contains the following items:

    • DotNetExpansions.dll
    • DotNetExpansions.xml (intellisense information)
    • A complete help file in chm format with samples for everything in the framework
    • A special guide in PDF format for the XMLIo class. This is a first issue of a new series I am working on. Once it’s ready it will be a complete book about the .Net Expansions Framework. Please give me feedback about the content and the design. 

    You can download the latest release of DotNet Expansions at
    http://cyrons.svn.beanstalkapp.com/general/DotNetExpansions/tags/

    or check out a copy of the project at
    http://cyrons.svn.beanstalkapp.com/general/DotNetExpansions/trunk 

    This framework has grown over years and maybe I would do some of the code in a different way today but nonetheless everything works fine – does it?

    Your feedback is appreciated.

    LEGAL NOTICE

    This software is GPL. Do with it whatever you want.

    Documentation copyright by Rainer Hilmer. You may not change or redistribute it without my permission.

    DotNetKicks-DE Image
    September 05

    Going to move my blog? (ziehe ich ziehe um?)

    MAYBE this will be my last entry on this blog. I’m going to move. The new blog URL:

    http://dotnet-forum.de/blogs/rainerhilmer/default.aspx

    Vielleicht wird das der letzte Beitrag in diesem Blog sein. Ich ziehe um. Die neue Blog-Adresse:

    http://dotnet-forum.de/blogs/rainerhilmer/default.aspx

    DotNetKicks-DE Image
    September 04

    Brain degeneration process unstoppable?

    Watch this video of a nuclear explosion and read the comments.

    http://www.youtube.com/watch?v=H1sS1TmXF38&feature=related

    It's just 20 years ago when everybody was still shocked by the view of a nuclear explosion - but here and now all I can see is stupid comments! 20 years ago we thought the end of mankind would be world war III where everything is nuked. Now I fear the end of mankind will be due to the loss of  brain power.

    August 16

    AntiFor patterns

    I have read Matthew Podwysocki’s AntiFor campaign article recently and I wondered wether it is possible to substitute all kinds of for-loops with LINQ expressions.
    This came back to my mind when I wrote a little application yesterday which consists of some for-loops.
    Here are the three methods with for-loops and their counterparts which use LINQ, so you can compare them directly.

    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
     
    namespace AntiForVersuche
    {
       public class AntiFor
       {
          //======================================================================
     
          // Spaltet einen multiline-Text in einzelne Zeilen und setzt diese in ein String-Array.
          public string[] SplitText2LinesWithFor(string text)
          {
             char[] splitCharacters = { '\n' };
             var lines = text.Split(splitCharacters);
             for(var i = 0; i < lines.Length; i++)
             {
                lines[i] = lines[i].TrimEnd('\r');
             }
             return lines;
          }
     
          public IEnumerable<string> SplitText2LinesWithLinq(string text)
          {
             char[] splitCharacters = { '\n' };
             if(text == null)
                return null;
             string[] lines = text.Split(splitCharacters);
             var query = lines.Select(value => value.TrimEnd('\r'));
             return query;
          }
     
          //======================================================================
     
          // Setzt drei Slashes vor jede Zeile.
          public string[] PrependXmlCommentSignsWithFor(string[] lines)
          {
             var result = new string[lines.Length];
             for(int i = 0; i < lines.Length; i++)
             {
                result[i] = "///" + lines[i];
             }
             return result;
          }
          
          public IEnumerable<string> PrependXmlCommentSignsWithLinq(IEnumerable<string> lines)
          {
             if(lines == null)
                return null;
             if(lines.Count() == 0)
                return null;
             var query = lines.Select(value => "///" + value);
             return query;
          }
     
          //======================================================================

     

    // Fügt die einzelnen Zeilen wieder zu einem Multiline-Text zusammen.

          public string ConcatenateLinesWithFor(string[] lines)
          {
             var builder = new StringBuilder();
             for(int i = 0; i < lines.Length; i++)
             {
                builder.AppendLine(lines[i]);
             }
             return builder.ToString();
          }
     
          public string ConcatenateLinesWithLinq(IEnumerable<string> lines)
          {
             if(lines == null)
                return null;
             if(lines.Count() == 0)
                return null;
             string query = lines.Aggregate((build, line) => build + "\r\n" + line);
             query += "\r\n";
             return query;
          }
       }
    }

    Conclusion


    Well, these three samples sure don’t answer my initial question to my utter satisfaction but they showed me some of the possibilities and I’m confident. It has been a pretty informative piece of work for me because I never used the Select and Aggregate Extension methods before. Maybe this blog post is as informative for some of you out there as it has been for me.
    August 11

    Software-Eigenmächtigkeiten

    Nicht nur daß die Installation von Windows Live Essentials meine IE-Startseite geändert hat, ohne mich zu fragen GRRRRRRR!!!!!!! >:-(

    nein, dieses Miststück von Messenger startet sich jedesmal bei dem Besuch einer Webseite, die irgendwas mit Windows live zu tun hat (wie z.B. mein Blog) von selber – obwohl ich in den Optionen eingestellt habe daß er das nicht soll!!!
    Ich schmeiß diesen Dreck in die Tonne!!!!!!!!

    Hat jemand ‘ne Gif-Animation die das Messenger-Icon erschießt?

    August 10

    NUnit, a competitor for MS-Test, which you shouldn’t let pass by without a closer look!

    I hesitated a long time. I use MS-Test, PEX and CHESS. Shouldn’t that be enough? Why the heck another unit test framework? Reading “The Curse of the Rewrite” from Tim Ross got me curious today (and most of all after I saw the code, wich uses NUnit). That blog post deals with testing of legacy code – but that’s just a side note.
    So what’s special about NUnit? Well, just look at this line of code:

    Assert.That(documentManager, Is.InstanceOfType(typeof(DocumentManager)), "Should create an instance of DocumentManager");


    It’s code which fulfills the BDD paradigm at last! So I downloaded and installed NUnit, looked at the syntax sample and I’m convinced ever since.
    If you have never heard of NUnit or still hesitate as I did before, than just take a look at this syntax sample. It’s right from the installation. I hope they don’t sue me for this. It’s pure advertising (but an honest one ;-) ).

    // ****************************************************************
    // Copyright 2007, Charlie Poole
    // This is free software licensed under the NUnit license. You may
    // obtain a copy of the license at http://nunit.org/?p=license&r=2.4
    // ****************************************************************
     
     
    // Cropped due to text length constraints in this blog. :-/
    // ...
    // The original code is much longer!
     
          [Test]
          public void IsTrue()
          {
             // Classic syntax
             Assert.IsTrue(2+2==4);
     
             // Helper syntax
             Assert.That(2+2==4, Is.True);
             Assert.That(2+2==4);
     
             // Inherited syntax
             Expect(2+2==4, True);
             Expect(2+2==4);
          }
     
          [Test]
          public void IsFalse()
          {
             // Classic syntax
             Assert.IsFalse(2+2==5);
     
             // Helper syntax
             Assert.That(2+2== 5, Is.False);
             
             // Inherited syntax
             Expect(2+2==5, False);
          }
     
    // Cropped due to text length constraints in this blog. :-/
    // ...
    // The original code is much longer!
          
          [Test]
          public void CollectionContainsTests()
          {
             int[] iarray = new int[] { 1, 2, 3 };
             string[] sarray = new string[] { "a", "b", "c" };
     
             // Classic syntax
             Assert.Contains(3, iarray);
             Assert.Contains("b", sarray);
             //...
     
             // Helper syntax
             Assert.That(iarray, Has.Member(3));
             Assert.That(sarray, Has.Member("b"));
             Assert.That(sarray, Has.No.Member("x"));
             //...
          
             // Inherited syntax
             Expect(iarray, Contains(3));
             Expect(sarray, Contains("b"));
             Expect(sarray, Not.Contains("x"));
     
             //...
          }
     
          // ...
     
          [Test]
          public void SubsetTests()
          {
             int[] ints1to5 = new int[] { 1, 2, 3, 4, 5 };
     
             // Classic syntax
             CollectionAssert.IsSubsetOf(new int[] { 1, 3, 5 }, ints1to5);
             CollectionAssert.IsSubsetOf(new int[] { 1, 2, 3, 4, 5 }, ints1to5);
             CollectionAssert.IsNotSubsetOf(new int[] { 2, 4, 6 }, ints1to5);
             CollectionAssert.IsNotSubsetOf(new int[] { 1, 2, 2, 2, 5 }, ints1to5);
     
             // Helper syntax
             Assert.That(new int[] { 1, 3, 5 }, Is.SubsetOf(ints1to5));
             Assert.That(new int[] { 1, 2, 3, 4, 5 }, Is.SubsetOf(ints1to5));
             Assert.That(new int[] { 2, 4, 6 }, Is.Not.SubsetOf(ints1to5));
          
             // Inherited syntax
             Expect(new int[] { 1, 3, 5 }, SubsetOf(ints1to5));
             Expect(new int[] { 1, 2, 3, 4, 5 }, SubsetOf(ints1to5));
             Expect(new int[] { 2, 4, 6 }, Not.SubsetOf(ints1to5));
          }
          #endregion
     
          #region Property Tests
          [Test]
          public void PropertyTests()
          {
             string[] array = { "abc", "bca", "xyz", "qrs" };
             string[] array2 = { "a", "ab", "abc" };
             ArrayList list = new ArrayList( array );
     
             // Not available using the classic syntax
     
             // Helper syntax
             Assert.That( list, Has.Property( "Count" ) );
             Assert.That( list, Has.No.Property( "Length" ) );
     
             // ...

    Do you know any other unit test framework which can beat this? Let me know!

    August 03

    Sample code in help files, namespace descriptions and angle brackets in XML comments. How to get around these hurdles.

    This tutorial will show you how to add sample code into your custom help files. It will not show you how to make help files in general because you can find several tutorials for this in the internet. Just one tip:
    Get Sandcastle from Microsoft at
    http://www.microsoft.com/downloads/details.aspx?familyid=E82EA71D-DA89-42EE-A715-696E3A4873B2&displaylang=en

    and the Sandcastle help file builder from codeplex.
    http://www.codeplex.com/SHFB

    You can also find a “Getting started” section in the Sandcastle help file builder documentation. 

    Now let's go:
    At first you must enable XML documentation file in the build section of the project properties.

    image

    Writing the sample code
    To add sample code, you have to write (and test!) it first, of course.

    using System;
     
    namespace XmlCommentSampleCodeDemo
    {
       class DemoCode
       {
          // This is the demo code which will be inserted into the XML-comment of the AddWorldTo-method.
          public void Demo()
          {
             MyClass instance = new MyClass();
             string myString = "Hello";
             string result = instance.AddWorldTo(myString);
             Console.WriteLine(result);
          }
       }
    }

    A demo class with inserted sample code
    Just copy and paste the demo code into the XML-comment block as shown below. Notice that the code is surrounded by a <code>-Section which is contained in an <example>-section.

    namespace XmlCommentSampleCodeDemo
    {
       /// <summary>
       /// This class is just a sample for the XML documentaion.
       /// You will see this line at the class level in the help file.
       /// </summary>
       public class MyClass
       {
          /// <summary>
          /// Adds " world." to a string.
          /// </summary>
          /// <param name="someString">Any string.</param>
          /// <returns>A modificated string.</returns>
          /// <example>
          /// <code>
          /// public void Demo()
          /// {
          ///    MyClass instance = new MyClass();
          ///    string myString = "Hello";
          ///    string result = instance.AddWorldTo(myString);
          ///    Console.WriteLine(result);
          /// }
          /// </code>
          /// </example>
          public string AddWorldTo(string someString)
          {
             return someString + " world.";
          }
       }
    }

     

    The help entry
    After generating the helpfile with Sandcastle help file builder, the entry for the AddWorldTo method will look like this:

    image

    Namespace comments
    Namespace comments are special because you cannot comment a namespace by default. Hopefully this will change with a future release of Visual Studio. Meanwhile you need the following trick to achieve this goal.

    namespace XmlCommentSampleCodeDemo
    {
       // Add this empty class in conjunction with this attribute to your project.
     
       /// <summary>
       /// This is just an entry to demonstrate a namespace comment.
       /// </summary>
       [System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
       class NamespaceDoc
       {
       }
    }

    Here you can see the comment for the namespace in the help.

    image 

    How to get angle brackets in a code example
    When you have angle brackets the compiler will warn you about inappropriate tags. This is simply because either the XML file with the comments as the code inside the comments use them together. To overcome this problem you have to surround the code with a <![CDATA[ ]]> tag.
    Here is an example:

    /// <summary>
          /// My method.
          /// </summary>
          /// <param name="stringList">The string list.</param>
          /// <example>
          /// <code>
          /// <![CDATA[
          /// // This is no usage example.
          /// public void MyMethod(List<string> stringList)
          /// {
          ///   //...
          /// }
          /// ]]>
          /// </code>
          /// </example>
          public void MyMethod(List<string> stringList)
          {
             //...
          }


    This is what it looks like in the help.
    image

    Note: You could also use HTML-tags for the angle brackets but

    1. it looks distracting and
    2. it’s likeley to forget or overlook them here and there. With the compilation time of the help file in mind, it’s very annoying when that happens.

    July 31

    Parallel file search, Part 4

    Hier noch ein paar Unit Tests. Darunter auch ein Concurrency-Test mittels CHESS.

    /**********************************************************************************************
     * In order to make these tests working correctly, they must run in MTA and not STA as usual. *
     * This modification is done in the LocalTestRun.testrunconfig with the                       * 
     * <ExecutionThread apartmentState="MTA" /> -tag.                                             *
     **********************************************************************************************/
    
    using System;
    using System.IO;
    using Cyrons.ParallelSearch;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace ParallelSearchTests
    {
       /// <summary>
       ///This is a test class for ParallelFileSearcher and is intended
       ///to contain all ParallelFileSearcher Unit Tests.
       ///</summary>
       [TestClass]
       public class ParallelFileSearcherTest
       {
          private static int eventCounter;
    
          /// <summary>
          ///Gets or sets the test context which provides
          ///information about and functionality for the current test run.
          ///</summary>
          public TestContext TestContext { get; set; }
          
          /// <summary>
          ///A test for GroupDriveItems
          ///</summary>
          [TestMethod]
          [DeploymentItem("ParallelSearch.dll")]
          public void ShouldCreate3FolderGroups()
          {
             var folders = new List<SearchSet>();
             var searchSet = new SearchSet(
                new DirectoryInfo(@"A:\"), SearchOption.TopDirectoryOnly);
             folders.Add(searchSet);
             searchSet = new SearchSet(
                new DirectoryInfo(@"B:\"), SearchOption.TopDirectoryOnly);
             folders.Add(searchSet);
             searchSet = new SearchSet(
                new DirectoryInfo(@"C:\"), SearchOption.TopDirectoryOnly);
             folders.Add(searchSet);
             /* A fourth entry is for a logical drive that's already in the list.
              * So even we have 4 entries, we should get 3 folder groups only. */
             searchSet = new SearchSet(
                new DirectoryInfo(@"C:\Program files"), SearchOption.TopDirectoryOnly);
             folders.Add(searchSet);
    
             IEnumerable<IGrouping<string, SearchSet>> actual
                = ParallelFileSearcher_Accessor.GroupDriveItems(folders);
             Assert.AreEqual(3, actual.Count());
          }
    
          [TestMethod]
          [HostType("Chess")]
          [TestProperty("ChessBreak", "BeforePreemption")]
          [TestProperty("ChessDebug", "true")]
          [Ignore] // Ran 2000 Threads without problems. Test has been aborted after that to save time.
          public void ChessParallelTest()
          {
             // See the remark at the top of the code.
             Console.WriteLine("Current thread state (should be MTA!): "
                + System.Threading.Thread.CurrentThread.GetApartmentState().ToString());
             const bool ignoreExceptions = true;
             var fileContentSearcher = new ParallelFileSearcher(ignoreExceptions);
             fileContentSearcher.FoundFile += NewFileFoundHandler;
             var searchSets = new List<SearchSet>();
             
             searchSets.Add(new SearchSet(
                new DirectoryInfo(@"C:\Program Files"),
                SearchOption.TopDirectoryOnly));
            
             searchSets.Add(
                new SearchSet(
                new DirectoryInfo(@"H:\VS2008\Lab\CyronsFramework"),
                SearchOption.AllDirectories));
             
             searchSets.Add(
                new SearchSet(
                new DirectoryInfo(@"I:\Developer\VS2008\Projects\Demos\Eigene\CallBackDelegateEssential"),
                SearchOption.AllDirectories));
    
             Console.WriteLine("Bitte warten. Dateien werden gesucht...");
    
             // Define search-parameters.
             const string filenamePattern = "*.cs";
             const string contentToSearchFor = ""; // Could also be null.
    
             // Start the search with those parameters.
             List<FileInfo> fileInfos =
                fileContentSearcher.FindFiles(
                searchSets, filenamePattern, contentToSearchFor, FindMode.FindAll);
             // Do something with fileInfos like, for instance, delete those files.
             Console.WriteLine(fileInfos.Count);
          }
    
          [TestMethod]
          public void ShouldGetCallOutEvent()
          {
             // Siehe Anmerkung.
             Console.WriteLine("Current thread state (should be MTA!): "
                + System.Threading.Thread.CurrentThread.GetApartmentState().ToString());
             // Create a search-list.
             var searchList = new List<SearchSet>();
    
             // Generate Search sets...
             var searchSet = new SearchSet(
                new DirectoryInfo(@"H:\VS2008\Lab\ActiveRecordPattern"),
                SearchOption.AllDirectories);
             // ...and add them to the search-list.
             searchList.Add(searchSet);
    
             searchSet = new SearchSet(
                new DirectoryInfo(@"I:\Developer\VS2008\Projects\Demos\Eigene\CallBackDelegateEssential"),
                SearchOption.AllDirectories);
             searchList.Add(searchSet);
    
             searchSet = new SearchSet(
                new DirectoryInfo(@"C:\Program Files"),
                SearchOption.TopDirectoryOnly);
             searchList.Add(searchSet);
    
             const bool ignoreExceptions = true;
             const string filenamePattern = "*.cs";
             const string contentToSearchFor = null;
             var parallelFileSearcher = new ParallelFileSearcher(ignoreExceptions);
             parallelFileSearcher.CallOutUnauthorizedAccess += ShowCurrentlyDeniedAccess;
             parallelFileSearcher.FoundFile += NewFileFoundHandler;
             List<FileInfo> files = parallelFileSearcher.FindFiles(
                searchList,
                filenamePattern,
                contentToSearchFor,
                FindMode.FindAll);
             Assert.IsTrue(eventCounter > 0);
          }
    
          [TestMethod]
          public void ShouldNotFailOnDeniedAccess()
          {
             var searchList = new List<SearchSet>();
             // The bad guy.
             var searchSet = new SearchSet(
                new DirectoryInfo(@"C:\System Volume Information"), SearchOption.TopDirectoryOnly);
             searchList.Add(searchSet);
             const bool ignoreExceptions = true;
             const string filenamePattern = "*.*";
             const string contentToSearchFor = null;
             var parallelFileSearcher = new ParallelFileSearcher(ignoreExceptions);
             parallelFileSearcher.CallOutUnauthorizedAccess += ShowCurrentlyDeniedAccess;
             parallelFileSearcher.FoundFile += NewFileFoundHandler;
             List<FileInfo> files = parallelFileSearcher.FindFiles(
                searchList,
                filenamePattern,
                contentToSearchFor,
                FindMode.FindAll);
             Assert.IsTrue(eventCounter > 0);
          }
    
          [TestMethod]
          public void ShouldNotFailOnInvalidPath()
          {
             var searchList = new List<SearchSet>();
             // The bad guy.
             var searchSet = new SearchSet(new DirectoryInfo(@"X:\"), SearchOption.AllDirectories);
             searchList.Add(searchSet);
             const bool ignoreExceptions = true;
             const string filenamePattern = "*.cs";
             const string contentToSearchFor = null;
             var parallelFileSearcher = new ParallelFileSearcher(ignoreExceptions);
             parallelFileSearcher.CallOutUnauthorizedAccess += ShowCurrentlyDeniedAccess;
             parallelFileSearcher.FoundFile += NewFileFoundHandler;
             List<FileInfo> files = parallelFileSearcher.FindFiles(
                searchList,
                filenamePattern,
                contentToSearchFor,
                FindMode.FindAll);
             Assert.IsTrue(files.Count == 0);
          }
    
          #region Helpers
    
          private static void ShowCurrentlyDeniedAccess(string fullName)
          {
             Console.WriteLine("Access denied for " + fullName);
             eventCounter++;
          }
    
          private static void NewFileFoundHandler(string fullFileName)
          {
             Console.WriteLine("found " + fullFileName);
          }
    
          #endregion
       }
    }
    

    Parallel file search, Part 3

    Zum Schluß noch ein kleines Demo, das zeigt wie einfach und intuitiv der ParallelFileSearcher zu verwenden ist:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using Cyrons.ParallelSearch;
    
    namespace ParallelSearchDemo
    {
       class Program
       {
          static void Main()
          {
             ShowFindFileContents();
    
             // Verhindert das selbsttätige Schließen des Konsolenfensters.
             Console.WriteLine("\nPress any key to terminate the program.");
             Console.ReadKey();
          }
    
          private static void ShowFindFileContents()
          {
             const bool ignoreExceptions = true;
             var parallelFileSearcher = new ParallelFileSearcher(ignoreExceptions);
             parallelFileSearcher.FoundFile += FileFoundHandler;
             parallelFileSearcher.CallOutUnauthorizedAccess += ShowCurrentlyDeniedAccess;
             
             // Make a container for the search sets.
             List<SearchSet> searchSets = new List<SearchSet>();
    
             // Generate a search set...
             var searchSet = new SearchSet(
                new DirectoryInfo(@"H:\VS2008\Lab\CyronsFramework"),
                SearchOption.AllDirectories);
             // ...and add it to the container.
             searchSets.Add(searchSet);
    
             // Generate another search set...
             searchSet = new SearchSet(
                new DirectoryInfo(@"I:\Developer\VS2008\Projects\Demos\Eigene"),
                SearchOption.AllDirectories);
             // ...and add it to the container.
             searchSets.Add(searchSet);
             
             // Define search-parameters.
             string filenamePattern = "*.cs";
             string contentToSearchFor = ""; // Could also be null.
    
             // Start the search with those parameters.
             Console.WriteLine("Search in progress. Please wait...");
             List<FileInfo> fileInfos =
                parallelFileSearcher.FindFiles(
                searchSets, filenamePattern, contentToSearchFor, FindMode.FindAll);
             // Do something with fileInfos like, for instance, delete those files.
          }
    
          static void ShowCurrentlyDeniedAccess(string fullName)
          {
             Console.WriteLine("Access denied on " + fullName);
          }
    
          static void FileFoundHandler(string fullFileName)
          {
             Console.WriteLine(fullFileName);
          }
       }
    }
    
    /* Sample Output:
    g:\projektinterkosmos\interkosmos\codecontractstrials\codecontractstrials\Program.cs
    g:\projektinterkosmos\interkosmos\codecontractstrials\codecontractstrials\SomeClass.cs
    
    h:\vs2008\lab\decoratorversuch\decoratorversuch\Program.cs
    
    i:\developer\vs2008\projects\demos\eigene\collectiveeventcontract\eineventvieleklassen\CollectiveEventArgs.cs
    i:\developer\vs2008\projects\demos\eigene\collectiveeventcontract\eineventvieleklassen\CollectiveEventContract.cs
    i:\developer\vs2008\projects\demos\eigene\collectiveeventcontract\eineventvieleklassen\Program.cs
    
    Press any key to terminate the program.
    */

    Parallel file search, Part 2

    ParallelFileSearcher-Klasse
    Das Herzstück des Ganzen.
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Security.Permissions;
    using System.Threading;
    
    namespace Cyrons.ParallelSearch
    {
       /// <summary>
       /// Sucht Dateien und/oder nach bestimmtem Inhalt in Dateien.
       /// Es können beliebig viele Suchstrings übergeben werden.
       /// Diese werden nach Laufwerksbuchstabe gruppiert.
       /// Alle Gruppen werden parallel durchsucht.
       /// Somit bietet diese Klasse eine optimale Performance.
       /// </summary>
       public class ParallelFileSearcher
       {
          #region Delegates
    
          /// <summary>
          /// Kapselt eine Methode, die aufgerufen wird, wenn die gesuchte Datei gefunden wurde.
          /// </summary>
          public delegate void FoundFileHandler(string fullFileName);
    
          /// <summary>
          /// Kapselt eine Methode, die aufgerufen wird,
          /// wenn versucht wurde, auf ein Verzeichnis zuzugreifen,
          /// für das keine Zugriffsberechtigung besteht.
          /// </summary>
          public delegate void CurrentAccessDeniedHandler(string fullName);
    
          #endregion
          #region Events
    
          /// <summary>
          /// Tritt ein wenn die gesuchte Datei gefunden wurde.
          /// </summary>
          public event FoundFileHandler FoundFile;
    
          /// <summary>
          /// Tritt ein wenn für den Zugriff auf ein Verzeichnis oder eine Datei keine Berechtigung besteht.
          /// </summary>
          public event CurrentAccessDeniedHandler CallOutUnauthorizedAccess;
    
          #endregion
          #region Fields
    
          private List<string> fileList = new List<string>();
    
          private volatile string content;
          private volatile bool found;
          private FindMode mode;
          private volatile string namePattern;
    
          #endregion Fields
          #region Constructors
    
          /// <summary>
          /// Initialisiert eine neue Instanz der <see cref="ParallelFileSearcher"/> Klasse.
          /// </summary>
          /// <param name="suppressExceptions">Wenn auf <c>true</c> gesetzt,
          /// werden IO-Exceptions ignoriert.</param>
          public ParallelFileSearcher(bool suppressExceptions)
          {
             SuppressExceptions = suppressExceptions;
          }
    
          #endregion Constructors
          #region Properties
    
          /// <summary>
          /// Ruft einen Wert ab, der angibt ob Exceptions ignoriert werden.
          /// </summary>
          /// <value>
          ///     <c>true</c> wenn Exceptions ignoriert werden; anderenfalls <c>false</c>.
          /// </value>
          public bool SuppressExceptions { get; private set; }
    
          #endregion Properties
          #region Methods
          #region Public Methods
          
          /// <summary>
          /// Findet Dateien und/oder bestimmten Inhalt von Dateien.
          /// </summary>
          /// <param name="searchSets">Eine Liste mit Suchsets (Instanzen der 
          /// <see cref="SearchSet"/>-Klasse.</param>
          /// <param name="filenamePattern">Ein Dateinamensmuster (z.B. *.cs).</param>
          /// <param name="contentToSearchFor">Der textuelle Inhalt, nach dem gesucht werden soll.</param>
          /// <param name="findMode">Der durch die 
          /// <see cref="FindMode"/>-Enumeration angegebene Suchmodus.</param>
          /// <returns>Eine Liste mit voll qualifizierten Dateinamen
          ///  (Instanzen der <see cref="FileInfo"/>-Klasse,
          ///  die den Suchkriterien entspricht.</returns>
          public List<FileInfo> FindFiles(
             List<SearchSet> searchSets, string filenamePattern,
             string contentToSearchFor, FindMode findMode)
          {
             var permission = new FileIOPermission(PermissionState.Unrestricted);
             permission.AllFiles = FileIOPermissionAccess.AllAccess;
             SetInstanceParameters(filenamePattern, contentToSearchFor, findMode);
             List<SearchSet> drivesAndFolders = GetDrivesAndFolders(searchSets);
             IEnumerable<IGrouping<string, SearchSet>> fileGroups = GroupDriveItems(drivesAndFolders);
             List<string> internalList = InvokeSearchBase(fileGroups);
             var fileInfos = new List<FileInfo>();
             foreach(var item in internalList)
             {
                fileInfos.Add(new FileInfo(item));
             }
             return fileInfos;
          }
    
          #endregion
          #region Private methods
    
          private static List<SearchSet> GetDrivesAndFolders(
             IEnumerable<SearchSet> searchSets)
          {
             var drivesAndFolders = new List<SearchSet>();
             foreach(var item in searchSets)
             {
                drivesAndFolders.Add(new SearchSet(item.DirectoryInformation, item.Recursive));
             }
             return drivesAndFolders;
          }
    
          private void GetFiles(IEnumerable<FileInfo> files)
          {
             // Wenn keine Content-Angabe vorhanden ist, soll der Inhalt der Files nicht durchsucht werden.
             if(!string.IsNullOrEmpty(content))
             {
                SearchFileContents(files);
             }
             else
             {
                found = true;
                foreach(var file in files)
                {
                   fileList.Add(file.FullName);
                   if(FoundFile != null)
                      FoundFile(file.FullName);
                }
             }
          }
    
          private void SearchFileContents(IEnumerable<FileInfo> files)
          {
             foreach(var file in files)
             {
                StreamReader stream = null;
                try
                {
                   stream = file.OpenText();
                   string fileContent = stream.ReadToEnd();
                   if(fileContent.Contains(content))
                   {
                      found = true;
                      fileList.Add(file.FullName);
                      if(FoundFile != null)
                         FoundFile(file.FullName);
                      if(mode == FindMode.FindOne)
                      {
                         stream.Close();
                         stream.Dispose();
                         break;
                      }
                   }
                }
                catch(IOException)
                {
                   if(!SuppressExceptions)
                      throw;
                }
                catch(UnauthorizedAccessException)
                {
                   if(!SuppressExceptions)
                      throw;
                   if(CallOutUnauthorizedAccess != null)
                      CallOutUnauthorizedAccess(file.FullName);
                }
                finally
                {
                   if(stream != null)
                   {
                      stream.Close();
                      stream.Dispose();
                   }
                }
             }
          }
    
          private void GetFileInfos(SearchSet container, IEnumerable<DirectoryInfo> directories)
          {
             foreach(var directory in directories)
             {
                try
                {
                   FileInfo[] files = directory.GetFiles(namePattern, container.Recursive);
                   if(files.Length > 0)
                      GetFiles(files);
                }
                catch(UnauthorizedAccessException)
                {
                   if(!SuppressExceptions)
                      throw;
                   if(CallOutUnauthorizedAccess != null)
                      CallOutUnauthorizedAccess(directory.FullName);
                }
                catch(IOException)
                {
                   if(!SuppressExceptions)
                      throw;
                }
             }
          }
    
          private static IEnumerable<IGrouping<string, SearchSet>> GroupDriveItems(
             IEnumerable<SearchSet> folders)
          {
             IEnumerable<IGrouping<string, SearchSet>> query =
                folders.GroupBy(drive => drive.DirectoryInformation.Root.ToString(), container => container);
             return query;
          }
    
          private void InvokeSearchByFileGroups(
             IEnumerable<IGrouping<string, SearchSet>> fileGroups,
             AutoResetEvent[] threadReadyEvents)
          {
             int counter = 0;
             foreach(IGrouping<string, SearchSet> fileGroup in fileGroups)
             {
                threadReadyEvents[counter] = new AutoResetEvent(false);
                IEnumerable<SearchSet> containers =
                   from items in fileGroup
                   select items;
                object parameters =
                   new SearchTaskParameters(containers, threadReadyEvents[counter]);
                if(!found)
                   ThreadPool.QueueUserWorkItem(Search, parameters);
                counter++;
             }
          }
    
          private List<string> InvokeSearchBase(
             IEnumerable<IGrouping<string, SearchSet>> fileGroups)
          {
             var threadReadyEvents = new AutoResetEvent[fileGroups.Count()];
             InvokeSearchByFileGroups(fileGroups, threadReadyEvents);
             WaitForThreads(threadReadyEvents);
             return fileList;
          }
    
          private void Search(object parameters)
          {
             if(found && mode == FindMode.FindOne)
                return;
             // Container auspacken -->>
             IEnumerable<SearchSet> folderGroup;
             AutoResetEvent doneEvent;
             UnpackParameterContainer(parameters, out folderGroup, out doneEvent);
             // <<--
             // Schleife für Folder innerhalb eines Laufwerks.
             foreach(SearchSet container in folderGroup)
             {
                var dir = new DirectoryInfo(container.DirectoryInformation.FullName);
                if(!dir.Exists)
                   break;
                dir.GetAccessControl();
                var directories = new DirectoryInfo[] { };
                try
                {
                   directories = dir.GetDirectories();
                }
                catch(UnauthorizedAccessException)
                {
                   if(!SuppressExceptions)
                      throw;
                   if(CallOutUnauthorizedAccess != null)
                      CallOutUnauthorizedAccess(container.DirectoryInformation.FullName);
                }
                GetFileInfos(container, directories);
             }
             doneEvent.Set();
          }
    
          private void SetInstanceParameters(
             string filenamePattern, string contentToSearchFor, FindMode findMode)
          {
             if(string.IsNullOrEmpty(filenamePattern))
                throw new ArgumentException("filenamePattern fehlt.");
             namePattern = filenamePattern;
             content = contentToSearchFor;
             mode = findMode;
          }
    
          private static void UnpackParameterContainer(
             object parameters, out IEnumerable<SearchSet> folderGroup,
             out AutoResetEvent doneEvent)
          {
             folderGroup = ((SearchTaskParameters)parameters).Containers;
             doneEvent = ((SearchTaskParameters)parameters).DoneEvent;
          }
    
          private void WaitForThreads(AutoResetEvent[] threadReadyEvents)
          {
             if(mode == FindMode.FindAll)
                WaitHandle.WaitAll(threadReadyEvents);
             else
                WaitHandle.WaitAny(threadReadyEvents);
          }
    
          #endregion Private methods
          #endregion Methods
       }
    }

    Parallel file search, Part 1

    Parallele Dateisuche? Natürlich macht es keinen Sinn, auf einem Laufwerk verschiedene Ordner gleichzeitig zu durchsuchen. Die Parallelität geht durch die Hardware-Struktur flöten – viel schlimmer noch, die Suche würde verlangsamt, weil der Laufwerks-Controller den Schreib-/Lesekopf unnötig viel hin und her bewegen würde. Diesen Umstand habe ich berücksichtigt. Nur Suchanfragen auf unterschiedlichen logischen Laufwerken werden wirklich parallel durchgeführt (wobei aber darauf zu achten ist, daß eine parallele Suche auf verschiedenen Partitionen einer physikalischen Festplatte ebenfalls keinen Geschwindigkeitsvorteil bringt). Suchstrings für gleiche Laufwerke werden dagegen sequentiell abgearbeitet. Bei der Angabe der Suchkriterien muß der Anwender darauf aber keine Rücksicht nehmen. Diese Gruppierungen werden automatisch unter der Haube vorgenommen.
    Wie funktioniert nun das Ganze?
    Es wird, wie gewohnt nach einer Datei gefragt (die üblichen Wildcards sind natürlich erlaubt) und es wird angegeben wo gesucht werden soll. Es muß aber, anders als bei Standard-Suchprogrammen, möglich sein, mehrere Orte für die Suche anzugeben. Das wird mittels sogenannter SearchSets organisiert. Dazu später mehr.
    Des weiteren kann ich mir zwei verschiedene Szenarien für eine Suche vorstellen.

    1. Man sucht nach einer bestimmten Datei und weiß daß sie nur an einem Ort sein kann, nur an welchem ist nicht klar. Oder man weiß eine Datei an verschiedenen Orten, wobei die Datei aber gleich ist. Nun wäre es in beiden Fällen doch sinnvoll wenn die Suche abgebrochen würde, sobald die Datei gefunden wurde.

    2. Man sucht alle Vorkommen einer bestimmten Datei, um z.B. redundante Dateien zu eliminieren. Hier macht es Sinn daß die Suche bis zum Ende durchläuft und alle Vorkommen zurück gibt.

    Für diese Szenarien gibt es die FindMode-Enumeration. Darüber kann angegeben werden, ob alle Suchtasks abgebrochen werden sollen, sobald einer die gewünschte Datei gefunden hat, oder ob alle Tasks ihre Aufgabe bis zum Ende durchziehen sollen.

    Hier nun die Komponenten von ParallelFileSearch im Einzelnen:

    Die FindMode-Enumeration
    Beschreibung siehe oben.

    namespace Cyrons.ParallelSearch
    {
       /// <summary>
       /// Gibt an ob die Suche nach dem ersten Fund abgebrochen werden soll,
       /// oder ob die Suche fortgeführt werden soll bis alle Pfade durchsucht sind.
       /// </summary>
       public enum FindMode
       {
          /// <summary>
          /// Stoppt die Suche wenn die erste Datei in einem der angegebenen Ordner gefunden wurde,
          /// die dem Suchkriterium entspricht.
          /// </summary>
          FindOne,
          /// <summary>
          /// Findet alle Dateien in allen angegebenen Ordnern, die dem Suchkriterium entsprechen.
          /// </summary>
          FindAll
       }
    }

    SearchSet-Klasse
    Diese Klasse hält die unterschiedlichen Pfadinformationen der Suchanfragen und die Suchoption für jede Anfrage (Rekursiv oder nicht).

    using System.IO;
    
    namespace Cyrons.ParallelSearch
    {
       /// <summary>
       /// Stellt einen Suchset, bestehend aus dem zu durchsuchenden Pfad
       /// und der Angabe, ob dieser Pfad rekursiv (inklusive Unterordnern) durchsucht werden soll.
       /// </summary>
       public class SearchSet
       {
          #region Constructors
    
          /// <summary>
          /// Initialisiert eine neue Instanz der <see cref="SearchSet"/> Klasse.
          /// </summary>
          /// <param name="directoryInfo">Eine Pfadangabe in Form einer 
          /// <see cref="DirectoryInfo"/>-Instanz.</param>
          /// <param name="recursive">Eine <see cref="SearchOption"/>-Enumeration,
          /// die angibt ob der Pfad rekursiv durchsucht werden soll.</param>
          public SearchSet(DirectoryInfo directoryInfo, SearchOption recursive)
          {
             DirectoryInformation = directoryInfo;
             Recursive = recursive;
          }
    
          #endregion Constructors
          #region Properties
    
          /// <summary>
          /// Ruft den aktuell zu durchsuchenden Pfad des Suchsets ab.
          /// </summary>
          /// <value>Eine Instanz der <see cref="DirectoryInfo"/>-Klasse.</value>
          public DirectoryInfo DirectoryInformation { get; private set; }
          
          /// <summary>
          /// Ruft die Suchoption des aktuellen Suchsets ab.
          /// </summary>
          /// <value>Eine der Enumerationen von <see cref="SearchOption"/>.</value>
          public SearchOption Recursive { get; private set; }
    
          #endregion Properties
       }
    }
     

    SearchTaskParameters-Klasse
    ParallelFileSearch arbeitet mit dem ThreadPool. QueueUserWorkItem erwartet neben dem WaitCallback-Delegaten einen Parameter vom Typ Objekt. Da ein Suchtask aber mit mehreren Parametern gefüttert werden muß, werden diese über die SearchTaskParameters-Klasse definiert und zusammengefasst. Innerhalb eines Threads werden diese dann wieder ausgepackt. Die SearchTaskParameters-Klasse ist internal deklariert, weil sie ausserhalb des APIs nicht sichtbar sein muß.

    using System.Collections.Generic;
    using System.Threading;
    
    namespace Cyrons.ParallelSearch
    {
       /// <summary>
       /// Beinhaltet Parameter, die an die Suchthreads übegeben werden.
       /// <remarks>
       /// Eine Methode die über den Threadpool gestartet wird,
       /// darf nur einen Parameter vom Typ Object haben.
       /// Mittels der ParameterContainer-Klasse wird diese Klippe elegant umschifft.
       /// </remarks>
       /// </summary>
       internal class SearchTaskParameters
       {
          #region Constructors
    
          /// <summary>
          /// Initialisiert eine neue Instanz der <see cref="SearchTaskParameters"/>-Klasse.
          /// </summary>
          /// <param name="containers">SearchSets</param>
          /// <param name="doneEvent">Jeder Thread bekommt ein 
          /// <see cref="AutoResetEvent"/> in's Gepäck.</param>
          internal SearchTaskParameters(
             IEnumerable<SearchSet> containers, AutoResetEvent doneEvent)
          {
             Containers = containers;
             DoneEvent = doneEvent;
          }
    
          #endregion Constructors
          #region Properties
    
          internal AutoResetEvent DoneEvent { get; private set; }
          internal IEnumerable<SearchSet> Containers { get; private set; }
    
          #endregion Properties
       }
    }
    July 24

    Directory.GetFiles und System Volume Information, der ewige Krieg

    Schreibt man eine Suchfunktion, die rekursiv ein NTFS-Laufwerk durchsuchen soll, kommt es zu einer UnauthorizedAccessException wenn der Algorithmus bei System Volume Information ankommt. So kann man das Problem lösen:

    using System;
    using System.Collections.Generic;
    using System.IO;
    
    namespace UnauthorizedAccessExceptionWorkAround
    {
       class Program
       {
          static void Main()
          {
             var someList = new List<string>();
             string filenamePattern = "*.txt";         
             string[] directories = Directory.GetDirectories("c:\\");
             Console.WriteLine("Searching, please wait..." + Environment.NewLine);
             foreach(string directory in directories)
             {
                try
                {               
                   someList.AddRange(Directory.GetFiles(
                                        directory, filenamePattern, SearchOption.AllDirectories));
                }
                catch(UnauthorizedAccessException)
                {
                   Console.WriteLine("Skipped directory {0}", directory);
                }
             }
    
             Console.WriteLine("=======================================================");
    
             if(someList.Count > 0)
             {
                Console.WriteLine(filenamePattern + " found at:");
                foreach(string item in someList)
                {
                   Console.WriteLine(item);
                }
             }
    
             // Verhindert das selbsttätige Schließen des Konsolenfensters.
             Console.WriteLine("\nPress any key to terminate the program.");
             Console.ReadKey();
          }
       }
    }

    Im Catch-Block könnte z.B. auch ein Event gefeuert, daß eine darüber liegende Applikation über den Vorfall informiert.
    July 14

    Musik kennt keine Grenzen

    Mal nichts über Software-Entwicklung, dafür aber mindestens genau so wichtig!

    http://vimeo.com/moogaloop.swf?clip_id=2539741

    April 10

    irbi.de Test bestanden

    Zitat:
    "IRBI steht für Internet-Risk-Behaviour-Index. Mit IRBI lernen Sie auf einfache und spielerische Weise, im Internet Gefahren zu vermeiden. Sie erproben, wie Sie sich online angemessen verhalten können."

    Um den IRBI-Test zu machen, muß man sich erst einmal mit seiner Email-Adresse registrieren. Nun, wie jeder weiß, sollte man seine persönlichen Daten nicht einfach so in's Internet streuen - dazu gehört auch die Email-Adresse! Darum habe ich mich nicht registriert und den Link zu irbi.de in die Tonne getreten. Test bestanden! ;-P

    March 20

    IE8 schlägt Crack für Office 2007 vor!

    Habt ihr schon den neuen Internet Explorer 8 installiert? Er bietet ein paar neue Funktionen. Dazu gehört "Vorgeschlagene Sites". Besucht man eine beliebige Webseite, schlägt diese Funktion weitere Seiten mit ähnlichem Inhalt vor, die für den User evtl. interessant sein könnten. Eben war ich auf InfoQ und klicke auf die Funktion. Seht selbst was dabei herausgekommen ist. :-D
     
     
    March 12

    Microsoft Certified Professional

    Seit heute bin ich einer. :-)

    MCP(rgb)

    January 08

    Korrektur mit derben Stilblüten!

    Ich bin gerade auf eine Korrekturseite des Trainingsbuches zu 70-528 gestoßen. Das haut dem Fass die Krone in's Gesicht!
    Nur ein Beispiel:
     

    Page 98: &lt;Beschriftung&gt; kann außerdem als einen Tag &lt;Beschriftung&gt; gerendert werden

    Lesen Absatz die Sekunde zu dem letzten Satz der Sekunde auf Seite 98:
     
    Alles klar???
    Mehr solche Klopper gibt es hier:
    Lesen und Kopf schütteln oder lachen - das ist Geschmacksache.