前口上
Task
でContinueWith
使ってたとき、コレ使えば、Select
、SelectMany
を構築できるんじゃなかろか?と突発的に思いついたので、勢いで描いてみた。
なお、実用性は皆無だし、とりあえず作れるのか?動くのか?が主目的なので何から何まで残念なことになってますから、その辺はご留意頂きたく。
拡張メソッドの作成
Selectを作る。
Select
メソッドはそんな難しくなかった。
public static Task<TResult> Select<TIn, TResult>(this Task<TIn> task, Func<TIn, TResult> selector)
=> task.ContinueWith(x => selector(x.Result));
無難に、ContinueWith
して、selector
を適用すれば良いだけ。
SelectManyを作る
こっちはちょっと厄介。
public static Task<TResult> SelectMany<TIn, TResult>(this Task<TIn> task, Func<TIn, Task<TResult>> selector) => task.ContinueWith(x =>
{
var t = selector(x.Result);
if (t.Status == TaskStatus.Created)
{
try
{
t.Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
t.Wait();
return t.Result;
});
public static Task<TProjected> SelectMany<TIn, TSelected, TProjected>(this Task<TIn> task,
Func<TIn, Task<TSelected>> selector,
Func<TIn, TSelected, TProjected> projector) =>
task.ContinueWith(x =>
{
var t = selector(x.Result);
if (t.Status == TaskStatus.Created)
{
try
{
t.Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
t.Wait();
return projector(x.Result, t.Result);
});
}
両方ともselector
がTask
を戻すので、こいつを元のtask
のContinueWith
内で待機させる必要がある。
で、待機させるにゃStart
してなきゃマズいけど、TryStart
なんて便利なもんはないので、とりあえず状態見てStart
叩いてこけたら無視!と言うガバガバな仕様。
さらに言うなら、失敗したとき、無限待機になったときなどは一切合切気にしてないので、その点もご注意頂ければ。。。
実際使ってみる
実用性は無いにせよ、使えなきゃアレなので、とりあえず使ってみた。
internal static void Main()
{
var a = Task.Run(() => 42);
var b = Task.Run(() => 84);
var ret = a.SelectMany(_ => b, (x, y) => x + y).Select(x => x.ToString());
ret.Wait();
Console.WriteLine(ret.Result);
ret = from x in Task.Run(() => 42)
from y in Task.Run(() => 84)
select (x + y).ToString();
ret.Wait();
Console.WriteLine(ret.Result);
}
とりあえず両方とも動いたので、まぁやりたいことは出来たかなって思います。
蛇足ながら、ContinueWith
のなかでこの場合は、b
やら、y
やらが待機する手前、実行時間は
各タスクの実行時間の総和となるので、実際の所実用性は皆無じゃないかと思います。
最後に
素人考えながら、多分この辺が作れて使えた以上、Task
はモナドなんじゃなかろか?
と思いました。
今回使ったソースコード
using System;
using System.Threading.Tasks;
namespace TestBench
{
static class TaskToLinkExtends
{
public static Task<TResult> Select<TIn, TResult>(this Task<TIn> task, Func<TIn, TResult> selector)
=> task.ContinueWith(x => selector(x.Result));
public static Task<TResult> SelectMany<TIn, TResult>(this Task<TIn> task, Func<TIn, Task<TResult>> selector)
=> task.ContinueWith(x =>
{
var t = selector(x.Result);
if (t.Status == TaskStatus.Created)
{
try
{
t.Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
t.Wait();
return t.Result;
});
public static Task<TProjected> SelectMany<TIn, TSelected, TProjected>(this Task<TIn> task,
Func<TIn, Task<TSelected>> selector,
Func<TIn, TSelected, TProjected> projector) =>
task.ContinueWith(x =>
{
var t = selector(x.Result);
if (t.Status == TaskStatus.Created)
{
try
{
t.Start();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
t.Wait();
return projector(x.Result, t.Result);
});
}
internal class Program
{
internal static void Main()
{
var a = Task.Run(() => 42);
var b = Task.Run(() => 84);
var ret = a.SelectMany(_ => b, (x, y) => x + y).Select(x => x.ToString());
ret.Wait();
Console.WriteLine(ret.Result);
ret = from x in Task.Run(() => 42)
from y in Task.Run(() => 84)
select (x + y).ToString();
ret.Wait();
Console.WriteLine(ret.Result);
}
}
}