views:

158

answers:

5

I'm trying to make a part of my code more fluent.

I have a string extension that makes an HTTP request out of the string and returns the response as a string. So I can do something like...

string _html = "http://www.stackoverflow.com".Request();

I'm trying to write an extension that will keep trying the request until it succeeds. My signature looks something like...

public static T KeepTrying<T>(this Func<T> KeepTryingThis) {
  // Code to ignore exceptions and keep trying goes here
  // Returns the result of KeepTryingThis if it succeeds
}

I intend to call it something like...

string _html = "http://www.stackoverflow.com".Request.KeepTrying();

Alas, that doesn't seem to work =). I tried making it into a lambda first but that doesn't seem to work either.

string _html = (() => "http://www.stackoverflow.com".Request()).KeepTrying();

Is there a way to do what I'm trying to do while keeping the syntax fairly fluent? Suggestions much appreciated.

Thanks.

+2  A: 

You can't use a method group for extension methods, or lambda expressions. I blogged about this a while ago.

I suspect you could cast to Func<string>:

string _html = ((Func<string>)"http://www.stackoverflow.com".Request)
                    .KeepTrying();

but that's pretty nasty.

One alternative would be to change Request() to return a Func, and use:

string _html = "http://www.stackoverflow.com".Request().KeepTrying();

Or if you wanted to keep the Request method itself simple, just add a RequestFunc method:

public static Func<string> RequestFunc(this string url)
{
    return () => url.Request();
}

and then call:

string _html = "http://www.stackoverflow.com".RequestFunc().KeepTrying();
Jon Skeet
+3  A: 

Why not turn this on its head?

  static T KeepTrying<T>(Func<T> func) {
        T val = default(T);
        while (true) {
            try {
                val = func();
                break;
            } catch { }
        }

        return val;
    }

    var html = KeepTrying(() => "http://www.stackoverflow.com".Request());
Sam Saffron
Darn - I'd hoped you could write it as var html = KeepTrying("...".Request), but method groups and generic type inference don't play together very nicely :(
Jon Skeet
That was going to be my last option.
fung
@sambo99 - I think it really is going to be my last option. Reason being for long lambdas, the end of KeepTrying is delimited by the closing parenthesis which can be a bit harder to spot sometimes. E.g. KeepTrying(() => _x.DoSomething().DoMoreStuff().AndThenSomeMore().PlusSomeOfThisStuff()).SubString(0, 5); Have upvoted anyway. I'll be using Skeet's last suggestion with some function chaining helpers.
fung
@fung, fair enough Jon's suggestion works fine. Personally I like having the fact that a block is being retried in the front, it seems a bit more consistent with foreach, while etc... but the closing parenthasis is a bit annoying.
Sam Saffron
+1  A: 

What about enhancing the Request?

string _html = "http://www.stackoverflow.com".Request(RequestOptions.KeepTrying);

string _html = "http://www.stackoverflow.com".Request(RequestOptions.Once);

RequestOptions is a enum. You could also have more options, timeout arguments, number of retries etc.

OR

public static string RepeatingRequest(this string url) {
  string response = null;
  while ( response != null /* how ever */ ) {
    response = url.Request();
  }
  return response;
}

string _html = "http://www.stackoverflow.com".RepeatingRequest();
Stefan Steinegger
As a generic method KeepTrying could be applied to any Func<T>, not just Request. Also, my opinion is that using enums takes a bit out of the fluency.
fung
This is nice as long as KeepTrying on a Request does not need special handling (eg. Exceptions) and additional arguments (timeout etc).
Stefan Steinegger
A: 

AFAIK you can write an extension method that extends a Func<T> delegate, but the compiler doesn't know what do you mean:

string _html = "http://www.stackoverflow.com".Request.KeepTrying(); // won't work

But if you explicitly cast the delegate will work:

string _html = ((Func<string>)"http://www.stackoverflow.com".Request).KeepTrying(); // works

The question here it whether the code readability is really improved in this case by an extension method.

Michael Damatov
A: 

I wouldn't write an extension method for string. Use a more specific type, like the Uri.

The full code:

public static class Extensions
{
    public static UriRequest Request(this Uri uri)
    {
        return new UriRequest(uri);
    }

    public static UriRequest KeepTrying(this UriRequest uriRequest)
    {
        uriRequest.KeepTrying = true;
        return uriRequest;
    }
}

public class UriRequest
{
    public Uri Uri { get; set; }
    public bool KeepTrying { get; set; }
    public UriRequest(Uri uri)
    {
        this.Uri = uri;
    }

    public string ToHtml()
    {
        var client = new System.Net.WebClient();

        do
        {
            try
            {
                using (var reader = new StreamReader(client.OpenRead(this.Uri)))
                {
                    return reader.ReadToEnd();
                }
            }
            catch (WebException ex)
            {
                // log ex
            }
        }
        while (KeepTrying);

        return null;
    }

    public static implicit operator string(UriRequest uriRequest)
    {
        return uriRequest.ToHtml();
    }    
}

Calling it:

 string html = new Uri("http://www.stackoverflow.com").Request().KeepTrying();
taoufik