7
\$\begingroup\$

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?

\$\endgroup\$
1
  • \$\begingroup\$ Aha. I just spent a while looking at this for you, hit the same wall. Interesting problems as you did, might come back and give it another try later! \$\endgroup\$
    – Michael A
    Jul 3, 2015 at 3:56

1 Answer 1

1
\$\begingroup\$

The problem here is that you want to be able to use a large number of unrelated objects (i.e. not implementing a common interface). One solution would be to define a usable interface and define wrapper implementations for each of these objects. Something like:

public interface IDataSource
{
    byte[] DownloadUrl(String url);
}

Your download class would use implementations of this interface, and you could provide implementations for HttpClient, WebClient, etc. like:

public class HttpClientDataSource : IDataSource
{
    public HttpClientDataSource(HttpClient client){}
}

All that said, this doesn't seem to really matter in your case. I'm not convinced that allocating an HttpClient for internal use is exactly wasteful of resources. Also, by forcing this decision onto the users of your library you've made it more complicated to use. You'll have to explain to your users how to choose between different HTTP implementations. Part of designing a good library is trading off flexibility and simplicity, and I would say the simpler interface is better.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.