Refactoring Halfwit's Coroutines to C# 5

In my previous post I showed a simple example of taking a Task-based async method and refactoring it to use C# 5's new async and await keywords.

Halfwit also makes use of my coroutine support in MadProps.MvvmLight, which like await is a way to write async code in such a way that it reads like sequentially-executed code.

Here's an example of using coroutines to follow or unfollow a user:

IEnumerable<IActivity> ToggleFollow(IHalfwitUser user)
{
    if (user == null) yield break;

    TwitterUserActivity toggleFollowActivity;

    if (_host.Friends.Any(f => f.ScreenName == user.ScreenName))
    {
        var mbox = new MessageBoxActivity("Unfollow " + user.Name + "?", "Confirm Unfollow", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
        yield return mbox;

        if (mbox.Result == MessageBoxResult.No) yield break;

        toggleFollowActivity = new UnfollowActivity(Service, user.ScreenName);
    }
    else
    {
        toggleFollowActivity = new FollowActivity(Service, user.ScreenName);
    }

    yield return toggleFollowActivity;

    if (toggleFollowActivity.Response.StatusCode != System.Net.HttpStatusCode.OK)
    {
        Status = toggleFollowActivity.Response.ErrorMessage;
        yield break;
    }

    var friend = _host.Friends.FirstOrDefault(u => u.ScreenName == toggleFollowActivity.User.ScreenName);
    if (friend == null)
    {
        _host.Friends.Add(new MainViewModel.IdAndScreenName
        {
            Id = toggleFollowActivity.User.Id,
            ScreenName = toggleFollowActivity.User.ScreenName,
        });
        Status = "Now following " + toggleFollowActivity.User.ScreenName + ".";
    }
    else
    {
        _host.Friends.Remove(friend);
        Status = "Unfollowed " + toggleFollowActivity.User.ScreenName;
    }
}

This makes use of three "activity" helper classes: the MessageBoxActivity class to show a confirmation dialog (if you're unfollowing) and the FollowActivity and UnfollowActivity classes to follow or unfollow the user.

Essentially each of these "activities" are executed asynchronously, and when they're done, control returns to the method to continue executing. This, of course, is pretty much exactly what the await keyword would do.

Here's the refactored method:

async void ToggleFollow(IHalfwitUser user)
{
    if (user == null) return;

    try
    {
        ITwitterResponse<TwitterUser> response;

        if (_host.Friends.Any(f => f.ScreenName == user.ScreenName))
        {
            if (MessageBoxResult.No == _confirmationDialog.Show("Unfollow " + user.Name + "?", "Confirm Unfollow"))
                return;

            response = await Service.UnfollowAsync(user.ScreenName);
        }
        else
        {
            response = await Service.FollowAsync(user.ScreenName);
        }

        if (response.StatusCode != System.Net.HttpStatusCode.OK)
        {
            Status = response.ErrorMessage;
            return;
        }

        var friend = _host.Friends.FirstOrDefault(u => u.ScreenName == response.Result.ScreenName);
        if (friend == null)
        {
            _host.Friends.Add(new MainViewModel.IdAndScreenName
            {
                Id = response.Result.Id,
                ScreenName = response.Result.ScreenName,
            });
            Status = "Now following " + response.Result.ScreenName + ".";
        }
        else
        {
            _host.Friends.Remove(friend);
            Status = "Unfollowed " + response.Result.ScreenName;
        }
    }
    catch (Exception ex)
    {
        Status = ex.GetBaseException().Message;
    }
}

As you can see, it's very similarly structured. A little bit more error handling in the form of a try/catch block.

You'll notice I introduced an IConfirmationDialog interface (and a default implementation) which simply shows the confirmation dialog box. That simplified things a lot before I even introduced the await keyword.

Writing the coroutine stuff in my MVVM Light helper library really helped me understand what the await keyword is doing behind the scenes. Well, conceptually anyway - I'm sure it's way more complicated than what I did. :) Regardless, it's good to be able to simplify the code even further this way.

halfwit c# .net mvvm
Posted by: Matt Hamilton
Last revised: 24 Apr, 2017 06:58 PM History

Trackbacks