Friday, August 28, 2015

Nice C# Code Abstractions

A little side C# project had me needing to read an XML file coming from an external source. It has been a while since I did battle with XML as my personal preference is for JSON wherever possible as I feel the latter is more about the data and less about the structure.

Anyway, this was an opportunity to try a Linq query against an XML file.

var xdoc = XDocument.Load(“path to XML file”);
List < Feed > newFeeds = (from lvl in xdoc.Descendants("outline")
                          select new Feed
                          {
                               FeedTitle = lvl.Attribute("title").Value,
                               RSSUrl = lvl.Attribute("xmlUrl").Value,
                               MainUrl = lvl.Attribute("htmlUrl").Value
                          }).ToList();

This statement reads an XML file and creates a list of classes (Feed) based upon the file content.

An elegant and clear method that shifts the processing from being about the file structure to concentrating on the data. OK – this is an abstraction – so things might well go wrong.

So course you can’t actually implement the code above no matter how elegant – you have to let the real world of dirty and incomplete files in. Something like:

var xdoc = XDocument.Load(“Path to XML file”);
List < Feed > newFeeds = (from lvl in xdoc.Descendants("outline")
                          where (string)lvl.Attribute("type") != null
                          select new Feed
     {
     FeedTitle = (string)lvl.Attribute("title") != null ? lvl.Attribute("title").Value : "",
     RSSUrl = (string)lvl.Attribute("xmlUrl") != null ? lvl.Attribute("xmlUrl").Value : "",
     MainUrl = (string)lvl.Attribute("htmlUrl") != null ? lvl.Attribute("htmlUrl").Value : ""
     }).ToList();

Where the objects (say lvl.Attribute("type")) is cast to a string and then that string is tested for null.

The same project had me playing around with some large strings where the .NET StringBuilder class is usually your friend. I needed to locate one or more occurrence of a given string and insert some additional characters near that location. The StringBuilder class has an Insert() method that can inject all sorts of types into a specified location BUT no inbuilt way to find that location.

Time for one of those new-fangled Extension methods which are very straightforward to implement.
I started with this:
 internal static class Extensions  
 {  
     public static int IndexOf(this StringBuilder sb, string value, int startIndex, bool ignoreCase)  
     {  
       int index;  
       int length = value.Length;  
       int maxSearchLength = (sb.Length - length) + 1;  
         for (int i = startIndex; i < maxSearchLength; ++i)  
         {  
           if (sb[i] == value[0] || (Char.ToLower(sb[i]) == Char.ToLower(value[0]) && ignoreCase))  
           {  
             index = 1;  
             while ((index < length) && (sb[i + index] == value[index] || (Char.ToLower(sb[i + index]) == Char.ToLower(value[index]) && ignoreCase)))  
               ++index;  
             if (index == length)  
               return i;  
           }  
         }  
         return -1;  
     }  
 }  

But I am lazy so then added the obvious alternate method signatures to the class:
 public static int IndexOf(this StringBuilder sb, string value, int startIndex)  
     {  
       return IndexOf(sb, value, startIndex, false);  
     }  
     public static int IndexOf(this StringBuilder sb, string value)  
     {  
       return IndexOf(sb, value, 0, false);  
     }  
     public static int IndexOf(this StringBuilder sb, string value, bool ignoreCase)  
     {  
       return IndexOf(sb, value, 0, ignoreCase);  
     }  

And it just worked:

sPos = myStringBuilder.IndexOf("<iframe", true);

This project also allowed me to try out an asynchronous method without the hassle of messing with Threads even wrapped as a BackgroundWorker.

await Task.Run(() => doFeedCheck()); // run doFeedCheck() on another thread

is clear, easy to understand and abstracts away a whole raft of code.

No comments: