Private Extension Methods

Monday, October 5, 2009

I spent a few hours on a failed experiment recently, but one concept that outlived the spike was how to make use of private extension methods. By private I mean the extension methods have a private access modifier and are only visible inside of a single class.

Sounds pointless, right?

Consider the code in the following class that is attempting to build multiple script tags out of the names of JavaScript files embedded as resources in an assembly.

public static class ScriptHelper
{
    public static string AsyncUploadScripts(this HtmlHelper helper)
    {
        var scriptNames = new string[] 
        {
            "Baz.Biz.js",
            "Bar.Foo.js",
            "Foo.Bar.js"
        };

        return scriptNames
            .Select(name =>
                _manager.GetWebResourceUrl(typeof(ScriptHelper), name))
            .Select(url => String.Format("... src='{0}' ...", url))
            .Aggregate(new StringBuilder(),
                       (sb, tag) => sb.Append(tag),
                        sb => sb.ToString());                          
    }

    // ...
}

That LINQ query is not pretty. Something with ON ERROR GOTO statements would be easier on the eyes.

I thought it would be nice if I could have descriptive names for the operations being performed. But, I didn’t want to create new extension methods for use outside the query.

Fortunately, extension methods as static private members work well inside the same class.

public static class ScriptHelper
{
    public static string AsyncUploadScripts(this HtmlHelper helper)
    {            
        var scriptTags = GetScriptResourceNames()
                            .MapNamesToResourceUrls()
                            .MapUrlsToScriptTags()
                            .CombineScriptTags();
        return scriptTags;                                              
    }

    static string CombineScriptTags(this IEnumerable<string> tags)
    {
        return tags.Aggregate(new StringBuilder(),
                    (sb, tag) => sb.Append(tag),
                    sb => sb.ToString());  
    }

    static IEnumerable<string> MapUrlsToScriptTags(
                                    this IEnumerable<string> urls)
    {
        return urls.Select(url =>
            String.Format("... src='{0}' ...", url));
    }

    static IEnumerable<string> MapNamesToResourceUrls(
                                    this IEnumerable<string> names)
    {
        return names.Select(name =>
                _manager.GetWebResourceUrl(typeof(ScriptHelper), name));
    }

    static IEnumerable<string> GetScriptResourceNames()
    {
        return new string[]
        {               
            "Baz.Bif.js",
            "Foo.Bar.js",
            "Zak.Wak.js",
        };            
    }

    // ...

The code is now in the junk pile, but the idea will stick with me forever … or at least until I switch to something else.


Comments
James Curran Tuesday, October 6, 2009
Clean and awesome.
gravatar Tom Tuesday, October 6, 2009
I've done a similar thing before, but I made them local function variables instead of extension methods, because I didn't want to clutter the class up with extra members.
gravatar S&#248;ren Skovsb&#248;ll Tuesday, October 6, 2009
How about using 'let'?

var scriptTags = from name in scriptNames
let resourceUrl = _manager.GetWebResourceUrl(typeof (ScriptHelper), name)
let scriptName = String.Format("... src='{0}' ...", resourceUrl)
select scriptName;
return string.Join("", scriptTags.ToArray());
gravatar scott Tuesday, October 6, 2009
@Tom: Good idea!

@Søren: I like that too!
gravatar Brian Genisio Wednesday, October 7, 2009
Yeah, I have used private extension methods like that before and it certainly makes the code more readable. But, what I wish I had were private extension methods within any (non-static) class.

For instance, I might have a private instance method like: if(IsSameAsPrevious(point)) that I would prefer to read more like this: if(point.IsSameAsPrevious()).

That particular extension method on point only makes sense within my current class, and is useless outside. Unfortunately, we can't write private extension methods that way :(
gravatar James Curran Friday, October 9, 2009
Hmmm... That's odd... I don't remember visiting here on Tuesday, nor do I remember seeing the article before today, nor, while it is a very cool idea, do I think I'd use the phrase "clean and awesome" to describe it.

So, ya'know you've got two "James Curran"s following your blog???



gravatar Scott Allen Monday, October 12, 2009
@James: I'd think that was odd, but I've run into like 5 Scott Allens on the Internet now. :)
gravatar Thanigainathan.S Monday, May 31, 2010
Hi,

Its very nice article.

Thanks,
Thanigainathan.S
Anonymous coward Monday, August 2, 2010
It's somewhat interesting that private extension methods work, yet...

Except for the .Aggregate call, the original looks prettier to me: I can see in 3 lines exactly what you're doing. In the second example, it doesn't even all fit on my screen at once. "MapUrlsToScriptTags" is only easier to read if you see and use it a lot -- smashing strings together to make a <script> tag is very recognizable.

I might combine the two Select calls, but I might not.

In place of the Aggregate(...), I'd just call .Join(). I don't remember offhand if that's in the stdlib, or if it's in one of my extension classes, but it's so ridiculously common it ought to be in the std.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!