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.
Trackbacks
- Dew Drop – June 13, 2012 (#1,345) | Alvin Ashcraft's Morning Dew | http://www.alvinashcraft.com/2012/06/13/dew-drop-june-13-2012-1345/
- Dew Drop – June 15, 2012 (#1,346) | Alvin Ashcraft's Morning Dew | http://www.alvinashcraft.com/2012/06/15/dew-drop-june-15-2012-1346/
- rimonabantexcellence site title | http://www.rimonabantexcellence.com/t.php?ahr0cdovl21hdhroyw1pbhrvbi5vcmcvagfszndpdc1hc3luyy1jb3jvdxrpbmvzp3jldmlzaw9upte=