Все асинхронные операции, требующие подписки на события для получения результатов, просто болезненны. Я думаю, что самый простой способ - абстрагировать обработку событий в несколько хороших методов расширения и использовать стиль передачи продолжения (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`
});
Конечный результат должен быть довольно простым в использовании и прочтении. Надеюсь, это поможет.