# Wednesday, February 20, 2008
So a few weeks back there was a post on the Genome TeamBlog which included a link to one of my lambda abuse posts from last year.

At any rate - the problem they faced was that with code like this:

DataContext Context = new DataContext();

string connStr = "";

 

DataDomainSchema schema = DataDomainSchema.LoadFrom("SomeMappingFile");

schema.CreateDbSchema(connStr);

 

DataDomain dd = new DataDomain(schema, connStr);

 

using (Context.Push(ShortRunningTransactionContext.Create()))

{

    Customer tt = dd.New<Customer>();

    tt.Name = "TechTalk";

 

    RootProject tt_hk = dd.New<RootProject>();

    tt_hk.Name = "Housekeeping";

 

    ChildProject tt_hk_hol = dd.New<ChildProject>();

    tt_hk_hol.Name = "Holiday";

    tt_hk.ChildProjects.Add(tt_hk_hol);

 

    ChildProject tt_hk_ill = dd.New<ChildProject>();

    tt_hk_ill.Name = "Illness";

 

    tt_hk.ChildProjects.Add(tt_hk_ill);

 

    tt.RootProjects.Add(tt_hk);

 

    RootProject tt_g = dd.New<RootProject>();

    tt_g.Name = "Genome";

 

    ChildProject tt_g_dev = dd.New<ChildProject>();

    tt_g_dev.Name = "Development";

    tt_g.ChildProjects.Add(tt_g_dev);

 

    ChildProject tt_g_mnt = dd.New<ChildProject>();

    tt_g_mnt.Name = "Maintenance";

    tt_g.ChildProjects.Add(tt_g_mnt);

    tt.RootProjects.Add(tt_g);

 

    Context.CommitCurrent();

}


You ended up with a very flat member initialization structure plagued with:
  • Having to explicitly name child instances being added to collections - there's a lot of unnecessary noise.
  • Where you can't easily see the structure i.e. it's not visually hierarchical, so at a glance you're not sure just what the structure is compared to say looking at an xml document with nested elements where it's quite obvious.
At any rate, the guys at the Genome project attempted to overcome this using the nested lambdas (what I coined a "DSL" at the time, though It's a terrible and inaccurate term for what's effectively just a bit of a "trick" relying on side effects of evaluation) - it didn't go so well though because of course at first glance the syntax look strongly typed, the reality is it's anything but, and refactoring tools just aren't going to do things like renaming of keys in the hash style syntax i.e. key => value, because the key is just a Lambda parameter.

But all is not lost - of course with C# 3.0 we already have a great syntax for doing this kind of hierarchical initialization, say for this set of types:

public class BlogPost

{

    private readonly List<string> _tags = new List<string>();

    private readonly BlogUser _createdBy = new BlogUser();       

 

    public string Title { get; set; }

 

    public string Body { get; set; }

 

    public List<string> Tags

    {

        get { return _tags; }

    }

 

    public BlogUser CreatedBy

    {

        get { return _createdBy; }

    }

 

    public BlogUser LastEditedBy { get; set; }

}

 

public class BlogUser

{

    public int Age { get; set; }

    public string Name { get; set; }

}


We could do something like this to initialize an instance of BlogPost:

BlogPost post = new BlogPost()

{

    Title = "Post on Lambdas",

    Body = "This is a post...",

    Tags =

    {

        ".Net",

        "Lambda",

        "C#3.0"

    },

    CreatedBy =

    {

        Name = "Jane Doe",

        Age = 35

    },

    LastEditedBy = new BlogUser()

    {

        Name = "Joe Bloggs",

        Age = 25

    }

};


But the catch for the Genome guys is that it looks like they need to construct their entities using their DataDomain class ...  I don't know about how there product works but I can only assume it's either to get a transparent proxy for change tracking purposes or to enlist it into the current session etc. (though if it's just to enlist the entity I can't see why they need to bother with getting the DataDomain to create a new instance, surely they could manually enlist it).

At any rate that's irrelevant :)

So I got to thinking that of course member initialization is one of the Lambda-friendly things we can do because it's expressed in a single statement - so we can happily take the above code snippet and express it like so:

BlogPost post = evaluator.Create(() => new BlogPost()

{

    Title = "Post on Lambdas",

    Body = "This is a post...",

    Tags =

    {

        ".Net",

        "Lambda",

        "C#3.0"

    },

    CreatedBy =

    {

        Name = "Jane Doe",

        Age = 35

    },

    LastEditedBy = new BlogUser()

    {                   

        Name = "Joe Bloggs",

        Age = 25

    }

});


Where evaluator is an instance of a class I wrote called ServiceInjectionEvaluator ... the Create method (as you can probably guess) has the following signature:


  public
T Create<T>(Expression<Func<T>> expression)


The service injection evaluator just relies on being configured with an IServiceProvider capable of resolving instances of types... at this point we just unwind the expression, substituting our own instance activation mechanism wherever we stumble upon a NewExpression and walking through the expressions executing each bit as required - though it makes the assumption that you're going to have either a New or MemberInit expression at the top level of the Lambda, otherwise we just compile the whole thing and throw it back without any changes (because I don't want to bother writing code to visit the other types of expression node).

public class ServiceInjectionEvaluator

{

    private readonly IServiceProvider _serviceProvider;

 

    public ServiceInjectionEvaluator(IServiceProvider serviceProvider)

    {

        _serviceProvider = serviceProvider;

    }

 

    public T Create<T>(Expression<Func<T>> expression)

    {

        switch (expression.Body.NodeType)

        {

            case ExpressionType.New:

            case ExpressionType.MemberInit:

                return (T)EvaluateExpression(expression.Body);

            default:

                return expression.Compile().Invoke();

        }

    }

 

    private object GetInstanceWithInit(MemberInitExpression expression)

    {

        object instance = GetInstance(expression.NewExpression);

        foreach (MemberBinding binding in expression.Bindings)

        {

            ApplyBinding(instance, binding);

        }

        return instance;

    }

 

    private void ApplyBinding(object instance, MemberBinding binding)

    {           

        switch (binding.BindingType)

        {                   

            case MemberBindingType.Assignment:

                ApplyAssignmentBinding(instance, (MemberAssignment)binding);

                break;

            case MemberBindingType.ListBinding:

                ApplyListBinding(instance, (MemberListBinding)binding);

                break;

            case MemberBindingType.MemberBinding:

                ApplyMemberBinding(instance, (MemberMemberBinding)binding);

                break;

            default:

                throw new NotImplementedException();

        }

    }

 

    private void ApplyMemberBinding(object instance, MemberMemberBinding binding)

    {

        PropertyInfo property = (PropertyInfo)binding.Member;

        object memberValue = property.GetValue(instance, null);

        foreach (MemberBinding childBinding in binding.Bindings)

        {

            ApplyBinding(memberValue, childBinding);

        }

    }

 

    private void ApplyListBinding(object instance, MemberListBinding binding)

    {

        object list = ((PropertyInfo)binding.Member).GetValue(instance, null);

 

        foreach (ElementInit elementInit in binding.Initializers)

        {

            Delegate compiled = Expression.Lambda(Expression.NewArrayInit(typeof(object), elementInit.Arguments.ToArray())).Compile();

            object[] arguments = (object[])compiled.DynamicInvoke();

            elementInit.AddMethod.Invoke(list, arguments);

        }

    }

 

    private void ApplyAssignmentBinding(object instance, MemberAssignment assignment)

    {

        object value = EvaluateExpression(assignment.Expression);

 

        PropertyInfo info = (PropertyInfo)assignment.Member;

        info.SetValue(instance, value, null);

    }

 

    private object EvaluateExpression(Expression expression)

    {

        switch (expression.NodeType)

        {

            case ExpressionType.New:

                return GetInstance((NewExpression) expression);

            case ExpressionType.MemberInit:

                return GetInstanceWithInit((MemberInitExpression) expression);

            default:

                return Expression.Lambda(expression).Compile().DynamicInvoke();

        }

    }

 

    private object GetInstance(NewExpression expression)

    {

        return _serviceProvider.GetService(expression.Type);

    }

}


The only thing left is to then either provide an existing instance of IServiceProvider (i.e. the Windsor container) or creating an adaptor

public class DataDomainServiceProvider : IServiceProvider

{

    private readonly DataDomain _domain;

    private static readonly MethodInfo member = typeof(DataDomain).GetMethod("New");

 

    public DataDomainServiceProvider(DataDomain domain)

    {

        _domain = domain;

    }

 

    public object GetService(Type serviceType)

    {           

        return member.MakeGenericMethod(serviceType).Invoke(_domain, null);           

    }

}


There's a few things the enterprising mind can do with substitutions and member initialization I can think of, especially around IoC - anyone else have some ideas or thoughts on using/abusing them?

.Net | C# 3.0 | LINQ
posted @ Wednesday, February 20, 2008 2:01:38 PM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | |
# Thursday, June 14, 2007
Here's the code for my last post on Annotations - I tidied up a few things up (it's still very basic but it does work) there's a single test fixture to give you a guide for usage... so things like:

[Test]
public void QueryStoreForClassAnnotationsWithCertainKey()
{
   
ClassA target1 = new ClassA();
    ClassA target2 = new ClassA();
    ClassA target3 = new ClassA();

    target1.Annotate(Description =>
"class number 1");
    target2.Annotate(Description => "class number 2");
    target3.Annotate(Parsed =>
true);

    var results = AnnotationStore.Classes
      .Where(a => a.HasKey(
"Description"))
      .ToList();

    Assert
.AreEqual(2, results.Count);
}

And also the equivalent thing is possible for members... though I suspect annotating members isn't all that useful in most cases...

[Test]
public void QueryStoreForMemberAnnotations()
{
    ClassA target1 = new ClassA();
    ClassA target2 = new ClassA();
    ClassA target3 = new ClassA();

    target1.Annotate(() => target1.FirstName, CamelCase => true); // annotating a property
    target1.Annotate(() => target1.Field, Ignored => true); // annotating a field
    target2.Annotate(() => target2.Execute(), Parsed => true); // annotating a method

    target3.Annotate(Parsed =>
true);

    var results = AnnotationStore.Members
      .Where(p => p.HasKey(
"CamelCase"))
      .ToList();

    Assert.AreEqual(1, results.Count);
}


.Net | C# 3.0 | LINQ
posted @ Thursday, June 14, 2007 4:06:12 PM (New Zealand Standard Time, UTC+12:00)    Comments [1] | |

So I've been mulling some ideas over after the whole abusing lambdas for Hash table construction (here and here) ... and after reading a post on Jb Evain's blog I decided to create a little bit of code for doing annotations... so given a class say:

public class ClassA
{
    public int Id { get; set; }
    public string Name { get; set; }
}

You can then do this kind of thing (assuming you've added the apropriate namespace where the Annotations static class resides in)

[Test]
public void AnnotateClass()
{
    ClassA classA = new ClassA();
    classA.Annotate(IsValid =>
false);
    classA.Annotate(MapsToTable =>
"TblClassA", Key => "Id");
    classA.Annotate(Roles =>
new [] {"Administrator", "User"});

    Assert.IsFalse(classA.Annotation<bool>("IsValid"));
    Assert.AreEqual("TblClassA", classA.Annotation<string>("MapsToTable"));
    Assert.AreEqual("Id", classA.Annotation<string>("Key"));
}

Or, perhaps you want to attach some annotations to a specific property... no problem!

[Test]
public void AnnotateProperty()
{
    ClassA classA = new ClassA();
    classA.Annotate(() => classA.Name, CanBeNull =>
true);
    bool canBeNull = classA.Annotation<bool>(() => classA.Name, "CanBeNull");
    Assert.AreEqual(true, canBeNull);
}

Under the hood the values are stored against a dictionary where the keys (in this case the instance of classA) are weak referenced... so once classA is garbage collected the entries in the dictionary will also dissapear in time (next time any method touches the dictionary).

The nice thing is obviously you can directly interogate the Annotations static class itself with a query expression to say find all objects with a certain annotation.

.Net | C# 3.0 | LINQ
posted @ Thursday, June 14, 2007 10:32:16 AM (New Zealand Standard Time, UTC+12:00)    Comments [2] | |
# Tuesday, June 12, 2007

So, I got a comment on the last post about hashes from lambdas (from Andrey Shchekin)... It pointed out the fact that you don't need to use expressions at all... which hilights an observation that I hadn't made myself - such that the lambda parameter names are available in the generated delegate... which of course makes perfect sense!

So given:

Func<string, T> func = Name => "Value";

You can get the lambda parameter "Name" from the function delegate by calling:

func.Method.GetParameters()[0].Name (would return "Name")

Here's the revised Hash method from Andrey:

public Dictionary<string, T> Hash<T>(params Func<string, T>[] args)
where T : class
{
   
var items = new Dictionary<string, T>();
   
foreach (var func in args)
    {
       
var item = func(null);
        items.Add(func.Method.GetParameters()[0].Name, item);
    }
   
return items;
}

very elegant and simple :)

He even did some stats, which I suspect are probably a lot more accurate then my inital observations:

For 10000 consecutive calls:

WithAdd 10.0144ms
WithLambdas 9713.968ms
WithLambdasConstantsOnly 240.3456ms
WithDelegates 30.0432ms

Now what about multiple parameters... so far I can't think of any uses I would have for it... perhaps a 2 level configuration dictionary?

[Test]
public void HashTwoLevelDict()
{
   
Dictionary<string, Dictionary<string, object>> config = this.Hash<object>(
     (Connection, DriverClass) =>
typeof(SqlClientDriver),
     (Dialect, DialectClass) =>
typeof(MsSql2000Dialect),
     (Connection, Provider) =>
typeof(DriverConnectionProvider),
     (Connection, ConnectionString) =>
"Data Source=.;Initial Catalog=test;Integrated Security=SSPI");

    Assert.AreEqual(typeof(SqlClientDriver), config["Connection"]["DriverClass"]);
}

Who knows... I look forward to seeing how Lambdas get used and abused for non-functional programming tasks :)
.Net | C# 3.0 | LINQ
posted @ Tuesday, June 12, 2007 1:22:21 PM (New Zealand Standard Time, UTC+12:00)    Comments [0] | |
# Monday, June 11, 2007
So first off... I'm surely not the first person to see the resemblance between a lambda expression and a hash table declaration in Ruby... so I had a go at populating a dictionary using lambda's... so given this:

[Test]
public void Evaluate()
{
    Dictionary<string, string> items = Hash(Name => "alex", Age => "10", Height => "20");
    Assert.AreEqual("alex", items["Name"]);
    Assert.AreEqual("10", items["Age"]);
    Assert.AreEqual("20", items["Height"]);
}

I got my desired result pretty quickly...

public Dictionary<string, T> Hash<T>(params Expression<Func<string, T>>[] args)
where T: class
{
    Dictionary<string, T> items = new Dictionary<string, T>();
    foreach (Expression<Func<string, T>> expression in args)
    {
       ConstantExpression itemValueExpression = expression.Body as ConstantExpression;
       if (itemValueExpression == null
       throw new InvalidCastException("The body of the expression must be of type ConstantExpression");
        T item = itemValueExpression.Value
as T;
        items.Add(expression.Parameters[0].Name, item);
    }
    return items;
}

But, then I tried this...

[Test]
public void EvaluateForObjects()
{
   
Dictionary<string, object> items = Hash<object>(Name => "alex", TargetType => typeof(Uri), Id => 10);
   
Assert.AreEqual(10, items["Id"]);
}

Which fails, the last key/value pair (Id => 10) isn't represented as a ConstantExpression, so to support it (and other eventualities) I modified my code a smidge...

public Dictionary<string, T> Hash<T>(params Expression<Func<string, T>>[] args)
where T: class
{
   
Dictionary<string, T> items = new Dictionary<string, T>();
   
foreach (Expression<Func<string, T>> expression in args)
   
{
       
ConstantExpression constantExpression = expression.Body as ConstantExpression;
        T item =
null;
       
if (constantExpression != null)
        {
          item = constantExpression.Value
as T;
        }
       
else
       
{
           
item = Expression.Lambda<Func<T>>(expression.Body).Compile()();
       
}
        items.Add(expression.Parameters[0].Name, item);
    }
    return items;
}

Now... let's compare performance to say, adding the values using the Add method... so... for a million consecutive executions (in milliseconds) a quick test yielded these results...

Execution # Using 'Hash' Using Dictionary.Add()
1 9718 1268
2 9588 1310
3 9636 1494

Which I suspect tells me nothing more then the fact that the "Hash" style initialization is a lot slower, but not slow enough to completely discourage me.


Edit: There seems to be a bit of interest in this lately, so don't forget to take a look at the other related posts if you found this interesting.

.Net | C# 3.0 | LINQ
posted @ Monday, June 11, 2007 9:32:15 PM (New Zealand Standard Time, UTC+12:00)    Comments [2] | |
# Tuesday, February 06, 2007
Just a quick post, for part two let's look at grouping our results for display purposes... first off, I grab only the episodes which we think are "valid" (in this case ones which are assigned a series, episode and part number)...

EpisodeParser episodeParser = new EpisodeParser();

 

YouTubeSearcher searcher = new YouTubeSearcher(DeveloperId);

 

// get a list of all the parts we are interested in

 

IEnumerable<EpisodePart> parts = (from result in searcher.QueryByTags("QI", "Quite", "Interesting")

                                select episodeParser.Parse(result))

                                .Where(p => p.SeriesNumber > 0 && p.EpisodeNumber > 0 && p.PartNumber > 0)

                                .Distinct();


Obviously what I've done here is probably rather poor style, surely the where clause could have been inside the select statement??... but I'm just trying to illustrate how you can mix the two notations... next we're going to group the results up using some nested "group by" and object projections... this is disgustingly easy, what more can you say about it.

// group those parts

var allSeries = from part in parts                           

                group part by part.SeriesNumber into series

                orderby series.Key

                select new { SeriesNumber = series.Key,

                    Episodes = from episodePart in series

                               group episodePart by episodePart.EpisodeNumber into episodes

                               orderby episodes.Key

                               select new {EpisodeNumber = episodes.Key,

                                       Parts = from chunk in episodes

                                               orderby chunk.PartNumber

                                               select chunk

                                       }             

                    };


Great, I think that saved us a lot of time, compared to doing it ourselves in C# 2.0...  let's now generate a little page to view the results - for now I'm just writing code in NUnit fixtures - so we'll use Console.WriteLine... though in a website I would probably just use some quite similar brail template code.

foreach (var series in allSeries)

{

    Console.WriteLine("<h3>Series {0}</h3>\r\n", series.SeriesNumber);

    foreach (var episode in series.Episodes)

    {                   

        Console.WriteLine("<h4>Episode {0}</h4>\r\n", episode.EpisodeNumber);

        foreach (var author in episode.Parts.GroupBy(p => p.Result.Author))

        {

            Console.WriteLine("<div>Author: <strong>{0}</strong>\r\n", author.Key);

            foreach(var part in author)

            {

                YouTubeResult result = part.Result;

                Console.WriteLine("<a href=\"{0}\">part {1} &nbsp;<img src=\"{2}\" /></a>", result.Url, part.PartNumber, result.ThumbUrl);

            }

        }

    }

}


I've also dumped the output from this to an html page, if you'd like to have a look at it.

Next time I might have a look at storing the results of the youtube query with base4 and writing some code for a little daemon which can identify newly posted episode parts on a periodic basis... which should all lead towards implementing the RSS feed capability I discussed in part one.

But for now I'm off to enjoy the sun!

posted @ Tuesday, February 06, 2007 10:51:59 AM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | |
# Friday, February 02, 2007
Well, the recent musings of Alex James have caught my interest around LINQ (part one and two) - and so I thought I would start having a play with LINQ and using it to query some well known web sites... and decided to go with dragging some info out of youtube...

Basically, I like QI (also known as Quite Interesting - british comedy show) - and there's a lot of episodes on youtube, querying on the tags "QI", "Quite" & "Interesting" returns about 1000 results... almost every result represents a part of an episode... and most people posting are kind enough to include something in the title or description of the video which lets you know which episode and series it belongs to...

However it's going to take some effort to actually start watching at series 1, episode 1 and work your way through the episodes in the right order by endlessly browsing youtube's search results... unless we start "value adding" - creating an "episodic" view over youtube's data.

At this point you could put your magic hat on and wish for features from you're youtube "episodes" site:
  • Perhaps an RSS feed notifying me of new episodes as they're listed on youtube daily.
  • A nice way to see a series, it's episodes, and each part that I need to play.
  • Maybe some aggregation of episode summaries from another web site for each episode (in this case I'm thinking maybe TVRage.Com...)
So you can quickly see that we can start making something virtually out of nothing, like MacGyver... but much lamer and without the hair.

First things first, I started having a look at YouTube's API - you can search by tags and get plenty of info back from their REST web service, but you can only get 20 results per page, 20 results isn't much cop so I built a simple class which gives access to the whole set of search results as an IEnumerable<YouTubeResult> - here's the code for that:

public class YouTubeSearcher

{

    private const string TagQuery = "http://www.youtube.com/api2_rest?method=youtube.videos.list_by_tag&dev_id={0}&tag={1}&page={2}";

    private string _developerId;

 

    public YouTubeSearcher(string developerId)

    {

        if (string.IsNullOrEmpty(developerId)) throw new ArgumentNullException("developerId");

        _developerId = developerId;

    }

 

    public IEnumerable<YouTubeResult> QueryByTags(params string[] tags)

    {

        if ((tags == null) || (tags.Length <= 0)) throw new ArgumentNullException("tags", "tags must contain one or more tags");

 

        for (int page=1; true; page++)

        {

            List<YouTubeResult> results = QueryByTagAndPage(JoinTags(tags), page);                               

            if (results.Count <= 0) break;

            foreach (YouTubeResult result in results) yield return result;

        }

    }

 

    private string JoinTags(string[] tags)

    {

        if (tags.Length == 1) return tags[0];

 

        StringBuilder builder = new StringBuilder(tags[0]);          

 

        for (int i=1; i<tags.Length; i++) builder.AppendFormat(",{0}", tags[i]);

 

        return builder.ToString();

    }

 

    private List<YouTubeResult> QueryByTagAndPage(string tag, int page)

    {

        Console.WriteLine("Querying by tag: {0}, page: {1}", tag, page);

 

        Stopwatch watch = Stopwatch.StartNew();

        try

        {

            List<YouTubeResult> results = new List<YouTubeResult>();

 

            string uri = string.Format(TagQuery, _developerId, tag, page);

            XPathDocument xpd = new XPathDocument(uri);

 

            XPathNavigator xpn = xpd.CreateNavigator();

 

            XPathNodeIterator xniError = xpn.Select(@"/ut_response");

 

            xniError.MoveNext();

 

            if (xniError.Current.GetAttribute("status", String.Empty) == "fail")

            {

                string expression = "/ut_response/error/description";

                string errorText = xpn.SelectSingleNode(expression).InnerXml;

 

                throw new YouTubeException("Error occured while querying youtube: {0}", errorText);

            }

 

            try

            {

                XPathNodeIterator xni =

                    xpn.Select(@"/ut_response/video_list/video");

 

                while (xni.MoveNext())

                {

                    XPathNavigator navigator = xni.Current;

 

                    string title = navigator.SelectSingleNode("title").InnerXml;

                    string url = navigator.SelectSingleNode("url").InnerXml;

                    string thumbUrl = navigator.SelectSingleNode("thumbnail_url").InnerXml;

                    string id = navigator.SelectSingleNode("id").InnerXml;

                    string description = navigator.SelectSingleNode("description").InnerXml;

                    int lengthInSeconds = int.Parse(navigator.SelectSingleNode("length_seconds").InnerXml);

                    string author = navigator.SelectSingleNode("author").InnerXml;

 

                    results.Add(new YouTubeResult(id, url, title, thumbUrl, lengthInSeconds, description, author));

                }

            }

            catch (XPathException xpe)

            {

                throw new YouTubeException("Xpath exception occured: {0}", xpe.Message);

            }

 

            return results;

        }

        finally

        {

            Console.WriteLine("Query complete in {0}ms", watch.ElapsedMilliseconds);

        }

    }     

}


Following on from that we need to parse each search result and attempt to pull out it's episode information:
  • Series Number
  • Episode Number
  • Part Number
At this point we might also make the assumption that parts should be grouped by the user who posted them - in case the same episode has been posted twice by two users (quite likely, people are silly).

Parsing part information could be done using successive LINQ queries, but It's actually not that pleasant considering we're generally interogating only two text fields - the title for the clip, and it's description - horses for courses - so instead I built a quick 'n dirty "EpisodeParser" class... here's the code for that:

public class EpisodeParser

{

    private List<AbstractContributor> _contributors = new List<AbstractContributor>();

 

    public EpisodeParser()

        : this(

        new SeriesContributor(),

        new EpisodeContributor(),

        new PartContributor(),

        new XFormatContributor(),

        new PilotContributor(),

        new PartOfPartsContributor(),

        new WordNumberPartsContributor())

    {

    }

 

    public EpisodeParser(params AbstractContributor[] contributors)

    {

        if (contributors != null) _contributors.AddRange(contributors);

    }

 

    public EpisodePart Parse(YouTubeResult result)

    {

        EpisodePart ep = new EpisodePart(result);

 

        foreach (AbstractContributor contributor in _contributors)

        {

            contributor.Contribute(ep);

        }

 

        return ep;

 

        /*

      * QI Series 4 EpisodePart 12 (part 3)

      * s2e10 part 1/4

      * Qi Series 1 Ep 5 Part 1/3

      * QI 2x01

      * Take Out 1

      * QI Pilot EpisodePart part 6

      * S2E09

      */  

    }

 

    private class SeriesContributor : AbstractContributor

    {

        public override void Contribute(EpisodePart episodePart)

        {

            int? seriesNumber = ParseNameNumber(episodePart, "series", "s");

            AssignSeriesNumber(episodePart, seriesNumber);

        }

    }

 

    private class EpisodeContributor : AbstractContributor

    {

        public override void Contribute(EpisodePart episodePart)

        {

            int? episodeNumber = ParseNameNumber(episodePart, "episode", "ep", "e");

            AssignEpisodeNumber(episodePart, episodeNumber);

        }

    }

 

    private class PartContributor : AbstractContributor

    {

        public override void Contribute(EpisodePart episodePart)

        {

            int? partNumber = ParseNameNumber(episodePart, "part", "p");

            AssignPartNumber(episodePart, partNumber);

        }

    }

 

    private class PilotContributor : AbstractContributor

    {

        public override void Contribute(EpisodePart episodePart)

        {

            if (episodePart.Result.Title.ToUpper().Contains("PILOT"))

            {

                AssignSeriesNumber(episodePart, 1);

                AssignEpisodeNumber(episodePart, 1);

            }

        }

    }

 

    private class XFormatContributor : AbstractContributor

    {

        public override void Contribute(EpisodePart episodePart)

        {

            int seriesNumber = 0;

            int episodeNumber = 0;

 

            if (SplitNumber(episodePart, ref seriesNumber, ref episodeNumber, 'X'))

            {

                AssignSeriesNumber(episodePart, seriesNumber);

                AssignEpisodeNumber(episodePart, episodeNumber);

            }

        }

    }

 

    private class PartOfPartsContributor : AbstractContributor

    {

        public override void Contribute(EpisodePart episodePart)

        {

            int partOf = 0;

            int parts = 0;

 

            if (SplitNumber(episodePart, ref partOf, ref parts, '/'))

            {

                AssignPartNumber(episodePart, partOf);

            }

        }

    }

 

    private class WordNumberPartsContributor : AbstractContributor

    {

        private static readonly Dictionary<string, int> _phrases;

 

        static WordNumberPartsContributor()

        {

            _phrases = new Dictionary<string, int>();

            _phrases.Add("part one", 1);

            _phrases.Add("part two", 1);

            _phrases.Add("part three", 1);

            _phrases.Add("part four", 1);

            _phrases.Add("part five", 1);

            _phrases.Add("part six", 1);

            _phrases.Add("part seven", 1);

            _phrases.Add("part eight", 1);

            _phrases.Add("part nine", 1);

            _phrases.Add("part ten", 1);

        }

 

        public override void Contribute(EpisodePart episodePart)

        {               

            AssignPartNumber(episodePart, FindPhrase(episodePart, _phrases));

        }

    }

}


At this point we have the building blocks for starting to write some LINQ queries... here's my first test - running a basic "select all"...

YouTubeSearcher searcher = new YouTubeSearcher(DeveloperId);

 

IEnumerable<YouTubeResult> results = from result

                                    in searcher.QueryByTags("QI", "Quite", "Interesting")                                               

                                    select result;

 

Console.WriteLine("total results: {0}", results.Count());


Trying a more explicit style of query, and parsing episodes:

EpisodeParser episodeParser = new EpisodeParser();

 

YouTubeSearcher searcher = new YouTubeSearcher(DeveloperId);

 

IEnumerable<EpisodePart> parts = searcher.QueryByTags("QI", "Quite", "Interesting")

    .Select(result => episodeParser.Parse(result))

    .Where(part => part.SeriesNumber == 1 && part.PartNumber == 1)

    .OrderBy(part => part.EpisodeNumber);

 

foreach (EpisodePart part in parts.Distinct())

{

    Console.WriteLine(part);               

}


Which will let us know which episodes in series 1 exist...

Bit of a rush, but next time I'll start digging in a little deeper...

At this point though it's worth noting that we have some stuff for free because of IEnumerable<T>...
  • Search results are being processed as their yielded, if we're just looking for the first matching item for a query we can stop without having to request additional result pages on a match is made.
  • Same goes for episodes, we only parse them as they are required - no unnecessary overhead.
So far nothing has required LINQ, but I think we'll start to see it being a great time saver come the next couple of parts... compared to writing the code ourselves.

We shall see!
posted @ Friday, February 02, 2007 9:22:25 PM (New Zealand Daylight Time, UTC+13:00)    Comments [0] | |
Search
FeedCount

Tags...
.Net (83) .Net Reactor (4) .net user groups (9) 2008SummerRoadTrip (1) ActiveRecord (1) architecture (1) architecture chat (95) ArchitectureCamp2007 (2) asp.net (1) Astoria (1) Auckland (1) base4 (9) batching (1) binsor (1) blog (4) boo (1) books (1) C# 3.0 (9) cambodia (9) CAML.Net (1) castle (40) china (8) codecamp (3) codeplex (3) dapper.net (1) DevDefined Ltd. (4) DirectShow.Net (1) DLR (1) DSL (4) EAUG (1) Enterprise Architect (5) Enterprise Architecture (1) Enterprise Library (1) F# (1) feedburner (2) first post (1) Friendster (1) generics (1) googlegears (1) hacks (3) hardware (3) hongkong (2) Horn (1) hyper-v (1) ideas (1) IoC (21) IronPython (13) IronRuby (2) jobs (1) Languages (2) laos (8) LINQ (7) LiveId (1) LLU (1) Local Government (1) MDA (1) MDD (1) microsoft (1) Model Driven Development (1) mono (1) monorail (2) Movies (1) Music (1) nDepend (1) news (1) NHibernate (3) NUnit (2) nvelocity (1) OAuth (6) office (1) OpenSocial (1) orcon (1) photos (1) php (1) PostSharp (1) powerpoint (1) presentations (1) ReSharper (1) REST (2) rhino commons (3) rhinomocks (5) Ruby (1) SaaS (1) scm (1) Screen Architect (0) SharePoint (5) silverlight (1) Splicer (4) SQL2008 (1) supcom (1) survey (1) svn (1) Syzmk (4) thailand (6) Tools (2) Tortoise SVN (1) trac (2) Travel (36) Unity (2) vietnam (7) vista (2) visual nhibernate (1) vmware (1) volta (3) VS2008 (1) WCF (3) wiki (2) wikipedia (1) Windows Server 2008 (1) windsor (6) WinForms (1) wix (2) WPF (2) xmlrpc (1) yahoo pipes (1)
Who am I?
Alex Henderson
Alex Henderson
Auckland, New Zealand
Managing Director at Dev|Defined Limited

"Self Confessed Coding Junky for 15 years"
View Alex Henderson's profile on LinkedIn
 
Mobile: +64-21-402-969
Email: bittercoder 'at' gmail 'dot' com
MSN: bittercoder_nz@hotmail
Skype: alex.devdefined
Navigation