LoginSignup
2
3

More than 5 years have passed since last update.

ノリと勢いでLINQ to Taskをでっち上げてみた。なお、実用性は皆無の模様

Last updated at Posted at 2016-05-08

前口上

TaskContinueWith使ってたとき、コレ使えば、SelectSelectManyを構築できるんじゃなかろか?と突発的に思いついたので、勢いで描いてみた。
なお、実用性は皆無だし、とりあえず作れるのか?動くのか?が主目的なので何から何まで残念なことになってますから、その辺はご留意頂きたく。

拡張メソッドの作成

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);
                });
    }

両方ともselectorTaskを戻すので、こいつを元のtaskContinueWith内で待機させる必要がある。
で、待機させるにゃ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);
        }

    }
}

2
3
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3