Wednesday, March 18, 2009

Using Extension Methods to Verify Assumptions

I was working on something with Tim yesterday, and between us we came up with what I think is a pretty cool trick. We were defining some extension methods on XElement and friends that make it easier to work work XHTML. Something like this:

doc.Html().Div(“foo”).Div(“bar”).Value

Which would pull out the text content of a div with a class of “bar” that was a descendant of a div with a class of “foo”. All well and good, but one of the problems we were trying to address is that we didn’t want to proceed if there was no div of class “foo”. A NullReferenceException would be thrown, but we wanted something more specific. So we came up with this:

public static T OrThrow<T>(this T obj, Exception e) {
    if (obj == null) {
        throw e; 
    } 
    return obj;
}

Which in turn allowed us to write this:

doc.Html().Div(“foo”).OrThrow(new MyException(“no foo”)).Div(“bar”).Value

The clever bit is that the OrThrow extension method is generic on the type it’s invoked on, and it returns that type, so you can insert it into a chained expression and you don’t lose any intellisense. And because of type inference, you don’t actually have to specify the type (you don’t have to type OrThrow<IEnumerable<XElement>>), which cleans up the expression a bit.

This approach is more general than just XHTML, of course. Really, it allows you to insert arbitrary assertions/checks into a series of chained calls using a fairly natural syntax (for some value of “natural”). Nothing you can’t do other ways, but we liked the way this came out enough to want to share it.

7 comments:

  1. Cool idea, thanks!

    That Div() extension also looks very convenient to use. Are you planning to publish and write about that when you're done, or is it something proprietary?

    ReplyDelete
  2. Unfortunately, it's not something I can really share, but the general idea is an extension method that looks like this:

    public static IEnumerable Div(this XElement parent, string class)
    {
    return from div
    in parent.Elements("div")
    where div.HasAttribute("class") && div.Attribute("class").Value.Equals(class)
    select div;
    }

    I haven't compiled this, but you get the idea. Define something similar for body, span, and whatever other tags you want, and you're off and running. You'll probably also want one that's an extension of IEnumerable but does basically the same thing.

    Honestly, I'd be pretty surprised if someone hasn't already published something like this.

    ReplyDelete
  3. That is very cool! I'll definitely be using that. Though I'd probably just give it a string parameter for the exception message and build the exception inside the method (if necessary).

    ReplyDelete
  4. Nice. It does allows you to provide extra info on what you want to validate. If you only needed to ignore the foo, I would go XPath instead :).

    root.XPathSelectElement("./[class=@foo]/[class=@bar]")

    Namespace: http://msdn.microsoft.com/en-us/library/bb156083.aspx

    ReplyDelete
  5. I put codes belown in my common class

    public static T Instance(this T obj) where T:new()
    {
    if (obj == null)
    {
    obj = new T();
    }

    return obj;
    }

    But i get the error "Constraints are not allowed on non-generic declarations"

    Thanks for your reply:)

    ReplyDelete
  6. That should be

    public static T Instance(this T obj) where T: new()

    The indicates that the method is generic on T.

    ReplyDelete
  7. 利用C#3.0提供的扩展方法技术,可以为已经编译好的程序集类型增加新的方法,从而应对新的扩展。除了在可扩展性方面所具有的优势之外,如果能够合理地结合泛型与类型推断,扩展方法还可以有效降低代码的重复,...

    ReplyDelete