2

I would like to ask how to convert a method so that it can return any type depending on what happens within it.

What I have is the start of an application which will visit a number of webpages (and ultimately perform some tasks on each) before moving on to the next.

At the minute I have this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

namespace WindowsFormsApplication12
{
public partial class Form1 : Form
{
    public Form1()
    {  
        InitializeComponent();
        webBrowser1.ScriptErrorsSuppressed = true;

        label1.MaximumSize = new Size(100, 0);
        label1.AutoSize = true;
    }

    private void go_Click(object sender, EventArgs e)
    {
        ProcessUrlsAsync(new[] { "http://google.com", "http://microsoft.com", "http://yahoo.com" }).Start();
    }

    private void Exit(object sender, EventArgs e)
    {
        Application.Exit();
    }


    private Task ProcessUrlsAsync(string[] urls)
    {
        return new Task(() =>
        {
            foreach (string url in urls)
            {
                TaskAwaiter<string> awaiter = ProcessUrlAsync(url);                  
                string result = awaiter.GetResult();

                label1.Invoke((MethodInvoker)(() => label1.Text = result));
            }
        });
    }

    private Task ProcessUrlsAsync(string url)
    {
        return new Task(() =>
        {
            TaskAwaiter<string> awaiter = ProcessUrlAsync(url);                    
            string result = awaiter.GetResult();

            MessageBox.Show(result);
        });
    }

    private TaskAwaiter<string> LoadWebPage(string url)
    {
        TaskCompletionSource<string> taskCompletionSource = new TaskCompletionSource<string>();
        var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
        {
            if (e.Url.Equals(url))
            {
                taskCompletionSource.SetResult(e.Url.ToString());
            }
            else
            {
                taskCompletionSource.SetResult(e.Url + ": " + webBrowser1.Document.Title);
            }
        });
        webBrowser1.DocumentCompleted += handler;
        taskCompletionSource.Task.ContinueWith(s => { webBrowser1.DocumentCompleted -= handler; });

        webBrowser1.Navigate(url);
        return taskCompletionSource.Task.GetAwaiter();
    }
}
}

What I'd like to do is change the

private TaskAwaiter<string> LoadWebPage(string url)

method so that it returns a TaskAwaiter< generic type >. My reason for this is that if it fails to log in or an error occurs whilst processing, then I can return a boolean or a string or an int to signify the success (or not) of the actions being performed.

I initially thought of overloading the method until I realised this would not work with different return types, doh!!

The references to the web pages are only there as a test to make sure I can get the basics of opening a web page. Once I have this, these will be replaced with my systems web pages before I can start processing.

Is it possible to convert this method in this way?

Edit: I like the look of @Bjarke Søgaard's solution so am trying to implement it.

Unfortunately I'm being a little dense at this time of night and for the life of me cannot work out how to use within the method. So far I have this:

TaskAwaiter<WebResponse> awaiter = LoadWebPage(url);


private TaskAwaiter<WebResponse> LoadWebPage(string url)
    {
        TaskCompletionSource<WebResponse> taskCompletionSource = new TaskCompletionSource<WebResponse>();
        var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
        {
            //taskCompletionSource.SetResult(e.Url + ": " + webBrowser1.Document.Title);
            taskCompletionSource.SetResult(<< Stuck Here >>);
        });
        webBrowser1.DocumentCompleted += handler;
        taskCompletionSource.Task.ContinueWith(s => { webBrowser1.DocumentCompleted -= handler; });

        webBrowser1.Navigate(url);
        return taskCompletionSource.Task.GetAwaiter();
    }

As you can see I'm at a loss as to how to set the result. Any help would be appreciated with this.

Thanks

5
  • 3
    Why do you need to use a generic type? Can't you use a TaskAwaiter<PageLoadResult>, where PageLoadResult is a helper struct with two fields: a string content, and, for example, a return code or result message (which is empty in case of everything went fine)?
    – Doc Brown
    Feb 18, 2017 at 22:04
  • 2
    Yeah, you don't want to return two different, unrelated types. Either return a PageLoadResult as Doc Brown mentioned, or look into using discriminated unions (which are a more advanced/functional way to solve this problem). Generic types will never be able to help you, since you'd have to declare the type you wanted out before you knew the result. You could use dynamic, but it's inappropriate in this situation.
    – mgw854
    Feb 18, 2017 at 22:10
  • 'TaskAwaiter<PageLoadResult>' sounded like an interesting solution however I found it quite difficult to find out about 'PageLoadResult' since good ol' google wasn't playing ball. However, as to why a generic type? Well it doesn't have to be; simply that I thought it would be the best and easiest solution....
    – cosmarchy
    Feb 19, 2017 at 20:16
  • There doesn't seem to be a http status code available from the event, so you could continue just branching with if (e.Url.Equals(url)) to make your response object
    – Caleth
    Feb 20, 2017 at 10:32
  • @Caleth strangely asking the browser to navigate to 'www.google.com' and then testing for '(e.Url.Equals(url)' and I find that I have something more in the order of 'google.co.uk/…' which makes it very difficult to test for. Not only that, I'm not sure why this happens as it doesn't come back with this when I do it in a regular browser!!
    – cosmarchy
    Feb 20, 2017 at 20:55

3 Answers 3

2

How about returning something like this?

public class WebResponse<T>
{
  public T Result { get; private set; }

  public HttpStatusCode ResponseCode { get; private set; }

  public WebResponse(T result, HttpStatusCode code)
  {
    this.Result = result;
    this.ResponseCode = code;
  }

  // In case of error
  public WebResponse(HttpStatusCode code)
  {
    this.ResponseCode = code;
  }

  public bool IsSuccess()
  {
    switch (this.ResponseCode)
    {
      case HttpStatusCode.Success:
      case HttpStatusCode.SuccessEmpty:
      case HttpStatusCode.Accepted:
        return true; // Possibly more
    }
    return false;
  }
}

This way, you can always use 'response.IsSuccess()' to check if your request went through all right. And if not, you can check ResponseCode for the reply.

Optionally you can add another string property that'll contain the raw response, for debugging purposes.

1

That's not what generics are

With a generic method, you are saying that there is an infinite family of overloads to you method, each returning a different type. Any specific call site will use one of these overloads, returning that specific type.

Your problem statement amounts to "I need to return either a string, or a boolean, or an int, at each call site", not "Some places I need a call that returns strings, other places a call that returns ints, and still others places that return bools"

You are already using generics how they are meant to be used

You have TaskAwaiter<T> and TaskCompletionSource<T> used in your method, which are families of classes that have a shared implementation, but provide methods that have different signatures, varying by what type is in place of T. Here you want some specific type, such as @Bjarke Søgaard's WebResponse<string>, which gets constructed from the parameters of the event.

0

Is it possible to convert this method in this way?

No. Because generic types are determined at compile time. In your case, it is only possible to determine the type at run time.

private TaskAwaiter<WebResponse> LoadWebPage(string url)
{
    TaskCompletionSource<WebResponse> taskCompletionSource = new TaskCompletionSource<WebResponse>();
    var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
    {
        //taskCompletionSource.SetResult(e.Url + ": " + webBrowser1.Document.Title);
        taskCompletionSource.SetResult(<< Stuck Here >>);
    });
    webBrowser1.DocumentCompleted += handler;
    taskCompletionSource.Task.ContinueWith(s => { webBrowser1.DocumentCompleted -= handler; });

    webBrowser1.Navigate(url);
    return taskCompletionSource.Task.GetAwaiter();
}

Why not using tasks instead of TaskAwaiter:

private Task<WebResponse> LoadWebPageAsync(string url)
{
    TaskCompletionSource<WebResponse> taskCompletionSource = new TaskCompletionSource<WebResponse>();
    var handler = new WebBrowserDocumentCompletedEventHandler((s, e) =>
    {
        //taskCompletionSource.SetResult(e.Url + ": " + webBrowser1.Document.Title);
        taskCompletionSource.SetResult(<< Stuck Here >>);
    });
    webBrowser1.DocumentCompleted += handler;
    taskCompletionSource.Task.ContinueWith(s => { webBrowser1.DocumentCompleted -= handler; });

    webBrowser1.Navigate(url);
    return taskCompletionSource.Task;
}

Usage:

private async Task ProcessUrlsAsync(string url)
{                  
    var result = await LoadWebPageAsync(url);
    // process result ...
}

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.