所有需要您订阅事件才能获得结果的异步操作都非常痛苦。我认为最简单的方法是将事件处理抽象为一些不错的扩展方法,并使用连续传递样式(CPS)来处理结果。
所以,首先要创建一个下载字符串的扩展方法:
public static void DownloadString(this Uri uri, Action<string> action)
{
if (uri == null) throw new ArgumentNullException("uri");
if (action == null) throw new ArgumentNullException("action");
var webclient = new WebClient();
DownloadStringCompletedEventHandler handler = null;
handler = (s, e) =>
{
var result = e.Result;
webclient.DownloadStringCompleted -= handler;
webclient.Dispose();
action(result);
};
webclient.DownloadStringCompleted += handler;
webclient.DownloadStringAsync(uri);
}
此方法隐藏了 WebClient 的创建、所有的事件处理以及之后的处置和取消订阅以进行清理。
它是这样使用的:
var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t =>
{
// Do something with the string
});
现在这可以用来创建GetBooks 方法。这里是:
public void GetBooks(Uri uri, Action<List<Books>> action)
{
if (action == null) throw new ArgumentNullException("action");
uri.DownloadString(t =>
{
var books = ParseJSON(t);
action(books);
});
}
它是这样使用的:
this.GetBooks(new Uri("http://someplace.com/books.json"), books =>
{
// Do something with `List<Books> books`
});
应该简洁明了。
现在,您可能希望通过几种方式扩展它。
您可以创建具有此签名的 ParseJSON 重载:
void ParseJSON(string text, Action<List<Books>> action)
然后你可以完全取消 GetBooks 方法,只写这个:
var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t => ParseJSON(t, books =>
{
// Do something with `List<Books> books`
// `string t` is also in scope here
}));
现在,您拥有了一套简洁流畅、可组合的操作。作为奖励,下载的字符串t也在范围内,因此您可以轻松记录它或在需要时进行一些其他处理。
您可能还需要处理异常,这些可以像这样添加:
public static void DownloadString(
this Uri uri,
Action<string> action,
Action<Exception> exception)
{
if (uri == null) throw new ArgumentNullException("uri");
if (action == null) throw new ArgumentNullException("action");
var webclient = (WebClient)null;
Action<Action> catcher = body =>
{
try
{
body();
}
catch (Exception ex)
{
ex.Data["uri"] = uri;
if (exception != null)
{
exception(ex);
}
}
finally
{
if (webclient != null)
{
webclient.Dispose();
}
}
};
var handler = (DownloadStringCompletedEventHandler)null;
handler = (s, e) =>
{
var result = (string)null;
catcher(() =>
{
result = e.Result;
webclient.DownloadStringCompleted -= handler;
});
action(result);
};
catcher(() =>
{
webclient = new WebClient();
webclient.DownloadStringCompleted += handler;
webclient.DownloadStringAsync(uri);
});
}
然后您可以将非错误处理DownloadString 扩展方法替换为:
public static void DownloadString(this Uri uri, Action<string> action)
{
uri.DownloadString(action, null);
}
然后要使用错误处理方法,您可以这样做:
var uri = new Uri("http://someplace.com/books.json");
uri.DownloadString(t => ParseJSON(t, books =>
{
// Do something with `List<Books> books`
}), ex =>
{
// Do something with `Exception ex`
});
最终结果应该非常易于使用和阅读。我希望这会有所帮助。