C# – Using Delegates to Avoid Duplicate Resource Creation

cdelegatesdesignfunctional programminglibraries

I'm writing a PCL that uses an HttpClient to go visit a few sites and extract data from them. My initial code looked like this:

public static class Download
{
    public async static Task<byte[]> FromYouTubeAsync(string videoUri)
    {
        using (var client = new HttpClient())
        {
            string source = await client
                .GetStringAsync(videoUri);

            // get links...

            return await client
                .GetByteArrayAsync(links.First());
        }
    }
}

I realized, however, that this would be a waste of resources if the user of my library already had a HttpClient in hand. For example:

// Caller code
using (var client = new HttpClient())
{
    // Do some work with client

    byte[] bytes = await Download.FromYouTubeAsync("some://uri"); // A second HttpClient is created here, wasting resources and time...

    // Do some work with client and bytes
}

One solution could be to add an overload taking an HttpClient as a parameter, but what if the user wanted to use a WebClient instead? Or an HttpWebRequest? Or some other kind of client that wasn't availsble in the PCL?

My solution

I decided to take delegate parameters to get the page source and to download the file based on the URI. My code now looks like this:

public static class Download
{
    public async static Task<byte[]> FromYouTubeAsync(string videoUri)
    {
        using (var client = new HttpClient())
        {
            return await FromYouTubeAsync(
                () => client
                .GetStringAsync(videoUri),
                uri => client
                .GetByteArrayAsync(uri));
        }
    }

    public async static Task<byte[]> FromYouTubeAsync(
        Func<Task<string>> sourceFactory, Func<string, Task<byte[]>> downloadFactory)
    {
        string source = await sourceFactory();

        // get links...

        return await downloadFactory(links.First());
    }
}

The problem

Hm. Wait, what if the caller is using a client that doesn't support async, like HttpWebRequest? Better add another overload taking a Func<string> and a Func<string, byte[]>. What if s/he wants to make the first call synchronous, but the second call async? Or vice versa? OK, just add 2 more overloads. Wait, what if the caller wants the whole operation to be synchronous? Let's go add 4 Download.FromYouTube overloads wrapping each of the async methods. Whoops, it turns out we need to visit another page of YouTube in case the video's signature is encrypted! Sure, let's go add 8 mor-


I think you can see where this is going. How do I maintain this flexibility without scaring the user away when Visual Studio says that there are 17 different overloads for this method?

Best Answer

Why not make FromYouTubeAsync be a pure function (e.g. which takes a String representing the HTML of the youtube video page, and which returns the URL of the mp4 video file it finds within), and then have the caller worry about how exactly to download bytes over the internet, if you think they want to have as much control over the process as you're implying?

You could always provide one default implementation using HttpClient for those clients who don't really care how it gets downloaded and just want as low-effort an API as possible.

Related Topic