В вашем примере DoSomething
компилятор не жалуется, потому что тип метода GetResult
вашего MyAwaitable не имеет ничего общего с THuh
. Утверждение, которое относится к THuh
, является return null;
. Нулевой литерал неявно конвертируется в THuh
, так что все хорошо.
Ключевое слово IEnumerable
, аналогичное await
, равно foreach
. await
требует тип, который соответствует определенному шаблону, так же, как и foreach
. Один - это механизм для использования ожидаемых типов, другой - для использования перечислимых типов.
С другой стороны, блоки итераторов (yield return
и yield break
) являются механизмами для определения перечислимых типов (путем написания метода, а не явного объявления типа). Аналогия здесь - ключевое слово async
.
Чтобы пояснить аналогию между async
и yield return
, обратите внимание, что блок итератора, который возвращает IEnumerable<int>
, может содержать оператор yield return 42;
, аналогично, асинхронный метод, который возвращает Task<int>
, может содержать оператор yield return 42;
. Обратите внимание, что в обоих случаях тип возвращаемого выражения является не типом возврата метода, а аргументом типа типа возвращаемого значения метода.
Если вы еще этого не сделали, вам действительно следует прочитать блог Эрика Липперта по следующим темам:
http://blogs.msdn.com/b/ericlippert/archive/tags/Async/
http://blogs.msdn.com/b/ericlippert/archive/tags/Iterators/
Кроме того, сообщения о стиле прохождения продолжения, кроме тех, что в серии Async, могут быть полезны, если концепция является новой для вас (как и для меня):
http://blogs.msdn.com/b/ericlippert/archive/tags/continuation+passing+style/
Наконец, см. Примеры в блоге Эрика со ссылкой на его статью MSDN и связанные статьи в том же выпуске и последующую статью Билла Вагнера в http://msdn.microsoft.com/en-us/vstudio/hh533273
EDIT
Я вижу некоторые примеры, которые, по-видимому, не соответствуют этому правилу - возвращаемое значение кажется достижимым и значение не по умолчанию. Это значение недоступно? Если так, то почему неуклюжий недоступный оператор return?
Фраза «конечная точка тела должна быть недоступна» означает, что вы должны иметь оператор return. Конечная точка тела идет после оператора return и становится недоступной оператором return. Пример использования обычного метода с возвратом int:
public int Main()
{
Console.WriteLine("X");
//after this comment is the reachable end point of the body; this method therefore won't compile.
}
public int Main()
{
Console.WriteLine("X");
return 0;
//anything after the return statement is unreachable, including the end point of the body; this method therefore will compile.
}
РЕДАКТИРОВАТЬ 2
Вот краткий, тривиальный пример ожидающего, который вычисляет последнюю половину строки. В примере передается продолжение, которое выводит результат на консоль. Это не потокобезопасно!
public static class StringExtensions
{
public static SubstringAwaiter GetAwaiter(this string s)
{
return new SubstringAwaiter(s, s.Length / 2, s.Length - s.Length / 2);
}
}
public class SubstringAwaiter
{
private readonly string _value;
private readonly int _start;
private readonly int _length;
private string _result;
private Action _continuation;
public SubstringAwaiter(string value, int start, int length)
{
_value = value;
_start = start;
_length = length;
}
public bool IsCompleted { get; private set; }
public void OnCompleted(Action callback)
{
if (callback == null)
return;
_continuation += callback;
}
public string GetResult()
{
if (!IsCompleted)
throw new InvalidOperationException();
return _result;
}
public void Execute()
{
_result = _value.Substring(_start, _length);
IsCompleted = true;
if (_continuation != null)
_continuation();
}
}
public class Program
{
public static void Main()
{
var awaiter = "HelloWorld".GetAwaiter();
awaiter.OnCompleted(() => Console.WriteLine(awaiter.GetResult()));
awaiter.Execute();
}
}