2014年6月26日

Task 成功時のみ ContinueWith

TaskTask<TResult> の成功(完了)時のみ ContinueWith させたいと思ったことはないだろうか。
下記パターンとか・・・
Task(1つ目の処理)
.ContinueWith(成功時の処理)
.ContinueWith(成功時の処理)
・・・
.ContinueWith(エラーをまとめて処理)

TaskContinuationOptions.OnlyOnRanToCompletion を ContinueWith の引数に入れてやれば、成功時のみは可能なのだが、ContinueWith のメソッドチェーンで実装が難しくなる。

var task = Task.Factory.StartNew(() => {
    Console.WriteLine("1st task");
    /* 略 */
    return 0;
}).ContinueWith(t => {
    // 1st task 成功時のみ
    Console.WriteLine("2nd task");
    /* 略 */
    return 0;
}, TaskContinuationOptions.OnlyOnRanToCompletion).ContinueWith(t => {
    // 前 task の続き(つまり、1st task 成功時のみ)
    Console.WriteLine("3rd task");
    /* 略 */
    return t.Result;
});

上記は、1st task 成功時のみ 2nd task → 3rd task の順で実行される。
1st task で例外が起こった場合、以降の Task は実行されず、
例外も未処理になってしまうため、task が GC される際にファイナライザで例外が発生する。(Webアプリとかで発生するとアプリが死ぬ奴)
※Task のエラー未処理対策は TaskScheduler.UnobservedTaskException

正しい方法としては、最初の Task を一度変数に格納して、そこから成功時・失敗時で別の Task に分離させていく。
→ 変数が増えてコードが複雑になる

では、ContinueWith 内で Task.IsFaulted なら Task.Exception を投げればいいかも?となるかもしれないが、ContinueWith 内に毎回同じ処理を書くのは面倒。
というわけで、次の拡張メソッドを作成。

解決案


/// 
/// task 成功時のみ completionFunc を実行する。
/// エラー・キャンセル情報は、次の Task に渡される。
/// beforeHandler が指定された(null でない)場合、task の Status に関わらず、最初に実行される。
/// 
public static Task<TOut> OnSuccess<TIn, TOut>(
    this Task<TIn> task, Func<TIn, TOut> completionFunc, Action<Task<TIn>> beforeHandler)
{
    return task.ContinueWith(t => t.OnSuccessImpl(completionFunc, beforeHandler))
        .Unwrap();
}

/// 
/// OnSuccess の実装。
/// Task 内で OnSuccess を使う場合は、こちらを使用すると Task ネストが深くなりにくい。
/// 
public static Task<TOut> OnSuccessImpl<TIn, TOut>(
    this Task<TIn> task, Func<TIn, TOut> completionFunc, Action<Task<TIn>> beforeHandler)
{
    if( beforeHandler != null ) beforeHandler(task);
    var tcs = new TaskCompletionSource<TOut>();
    switch( task.Status ){
    case TaskStatus.Canceled:
        tcs.TrySetCanceled();
        break;
    case TaskStatus.Faulted:
        tcs.TrySetException(task.Exception.InnerExceptions);
        break;
    case TaskStatus.RanToCompletion:
        TOut result;
        try {
            result = completionFunc(task.Result);
        } catch( Exception ex ){
            tcs.TrySetException(ex);
            break;
        }
        tcs.TrySetResult(result);
        break;
    }
    return tcs.Task;
}
[使用例]

var task = Task.Factory.StartNew(() => {
    Console.WriteLine("1st task");
    var len = ((string)null).Length; //例外発生
    return 0;
}).OnSuccess(t => {
    // 1st task 成功時のみ
    Console.WriteLine("success task");
    return 0;
}, null).ContinueWith(t => {
    // 必ず実行
    Console.WriteLine("error handling");
    return t.Result;
});
Console.WriteLine(task.Result);

//[結果]
// 1st task
// error handling
//
// 1st task の例外メッセージ

また、このメソッドはエラーを次の Task に流すため、次の効果がある。
  • エラー未処理バグが減る
  • AggregateException が深くならない

2014年6月21日

構造体(struct)で再帰的定義

.NET の構造体は、自身をプロパティとして持つことはできない。(クラスなら可能)
理由は単純で、構造体は値型で null にならないため、構造体のサイズが無限になってしまうためである。
(自身をプロパティに持つと、そのプロパティがさらに自身をプロパティとして持ち・・・と無限に続く)

// 自身をプロパティとして持つ例
// ※コンパイルエラーになる
struct Node
{
    public int Value;
    public Node Next;
}

代替案1 クラス

構造体の代わりにクラスを使う。
クラスは null になるため、再帰的定義が可能。

class Node
{
    public int Value;
    public Node Next;
}

代替案2 構造体ポインタ

どうしても構造体を使いたい場合、unsafe でプロパティを構造体のポインタを型とする。
ポインタだと null になれるので、構造体のサイズが無限になることもない。
ただし、unsafe が必要なのでコードが汚くなる。
unsafe が使えない VB.NET では不可。

unsafe struct Node
{
    public int Value;
    public Node* Next;
}

static class Program
{
    static void Main()
    {
        // 使用例
        var node = new Node() { Value = 1 };
        unsafe {
            node.Next = &node;
            Console.WriteLine(node.Value); // 1
            Console.WriteLine(node.Next->Value); // 1
            Console.WriteLine(node.Next->Next->Value); // 1
        }
    }
}

上記例で構造体ポインタをプロパティで隠せばいいかも、と思うかもしれないが無理。
C# のプロパティは参照渡し不可。

2014年6月16日

クラス(class) vs 構造体(struct)

.NET の型は、クラス(class/Class)構造体(struct/Structure)の2つに大きく分けられる。

クラスは参照型とも呼ばれ、その変数はデータがあるメモリへのアドレス(参照)を保持している。
構造体は値型とも呼ばれ、その変数はデータを直接保持している。
クラスのデータが置かれる場所をヒープ、構造体のデータが置かれる場所をスタックと呼ぶ。

クラスと構造体は両方ともデータとメソッドを持てるので、慣れないうちはどちらを使えばいいか迷うと思う。
とりあえず、使い分けの基準になりそうな違いを挙げる。
  1. 代入(引数への値渡し)時と、ボックス化&ボックス化解除(キャスト)時、構造体はデータをコピーする
    クラスはアドレスのコピーのみ
    ※ 構造体のサイズが大きいほど、代入のコストは大きくなる
  2. 構造体は null にならない
    ※ null とはクラス変数がデータ(参照先)を持ってないことを表すアドレス
  3. インスタンス作成(new)の速度は、クラスより構造体の方が速い
  4. 構造体は GC の対象にならない (スコープから外れた時点で、自動的に消える)
  5. 構造体は他の構造体やクラスから継承できず、継承元にもならない (ただし、インターフェースは実装可能)

補足

  1. についてコードで説明

class ElementC
{
    public ElementC(int value) { Value = value; }
    public int Value;
}
struct ElementS
{
    public ElementS(int value) { Value = value; }
    public int Value;
}
static class Program
{
    static void Main(string[] args)
    {
        /* クラス変数の代入 */
        var ec = new ElementC(1);
        Console.WriteLine(ec.Value);  // 1
        var ec_ = ec; /* クラスの代入はアドレスのコピー */
        ec_.Value = 2; /* 同じデータを指しているので ec_ を変更すると ec も変わる */
        /* 結果 */
        Console.WriteLine(ec_.Value); // 2
        Console.WriteLine(ec.Value);  // 2

        /* 構造体変数の代入 */
        var es = new ElementS(10);
        Console.WriteLine(es.Value);  // 10
        var es_ = es; /* 構造体の代入はデータのコピー */
        es_.Value = 20; /* コピーなので es_ を変更しても es には変わらない */
        /* 結果 */
        Console.WriteLine(es_.Value); // 20
        Console.WriteLine(es.Value);  // 10
    }
}
引数の値渡しも代入と同様。「=」が無いため、意識しにくいかも。
List<T> 使用時の間違い易いパターン。

/* ElementC, ElementS の定義は上と同じ */
static class Program
{
    static void Main(string[] args)
    {
        /* クラス変数の値渡し */
        var ecs = new List<ElementC>();
        var ec = new ElementC(1);
        ecs.Add(ec); /* アドレスのコピー */
        Console.WriteLine(ecs[0].Value); // 1
        ec.Value = 2; /* ec を変更すると ecs[0] も変わる */
        /* 結果 */
        Console.WriteLine(ec.Value);     // 2
        Console.WriteLine(ecs[0].Value); // 2

        /* 構造体変数の値渡し */
        var ess = new List<ElementS>();
        var es = new ElementS(10);
        ess.Add(es); /* データのコピー */
        Console.WriteLine(ess[0].Value); // 10
        es.Value = 20; /* es を変更しても ess[0] は変わらない*/
        /* 結果 */
        Console.WriteLine(es.Value);     // 20
        Console.WriteLine(ess[0].Value); // 10
    }
}

  1. について
例外的な構造体として、Nullable<T> が存在するが、これは本当に null になるわけではない。
Nullable<T> は下記のように定義されてる。

public struct Nullable<T> where T : struct
{
    private bool hasValue;
    internal T value;
    /* 以降、省略 */
}
Nullable<T> が null を代入できたり、null と比較できたりするのは、コンパイラが該当する処理に置き換えているためである。
つまり、シンタックスシュガーになっている。

その他の違い

下記が分かり易くまとめられてる。

おまけ

unsafe を使えば、構造体のアドレスを取得できる。
ちょっと反則っぽいが・・・

/* ElementS の定義は上と同じ */
static class Program
{
    static void Main(string[] args)
    {
        var es = new ElementS(10);
        Console.WriteLine(es.Value);  // 10
        unsafe {
            var esp = &es;   /* es のアドレス取得 */
            esp->Value = 30; /* esp の参照先を変更すると、es も変わる */
            Console.WriteLine(esp->Value); // 30
        }
        Console.WriteLine(es.Value); // 30
    }
}

ちなみに、es を object 変数に代入し、その変数を ElementS でキャストして Value を変更するとかはできない。
コンパイルエラーになる。(コンパイラ エラー CS0445

2014年6月7日

IE9.js(ie7-js) でスタイルシートの解析を部分的に無視

記事タイトルのような機能が無かったため、機能追加したものを作成。
https://bitbucket.org/temp_impl/ie9-js/src

オリジナルとの差分
https://bitbucket.org/temp_impl/ie9-js/commits/124d43c6f6d12327aa0c8e20775ba6a106ea2de0

使用方法

<style> や <link> に class="ie9js-ignore" を付けると、スタイルシート解析の対象外になる。
「class に 対象クラス名が含まれているか」で判定しているので、クラス名が複数あってもOK。

<html>
<head>
<!-- スタイルシート解析から無視される -->
<link type="text/css" rel="stylesheet" href="style.ignore.css" class="ie9js-ignore" />
<style type="text/css" class="ie9js-ignore">
div > div:first-child {
    background-color: cyan;
}
</style>

<!-- スタイルシート解析される -->
<link type="text/css" rel="stylesheet" href="style.css" />
<style type="text/css">
div > div:last-child {
    background-color: yellow;
}
</style>

<!--[if lt IE 9]>
<script src="IE9.min.js"></script>
<![endif]-->
</head>

<body>
  <div>
    <div>first</div>
    <div>last</div>
  </div>
</body>
</html>

使いどころ

対象ブラウザ(IE7/IE8)で元から使えるスタイルシートは解析させる必要がないので無視できる。
解析されて javascript 適用のスタイルシートになると、そのままでは DHTML で使えないなどの問題が起こったりする。

また、ファイルサイズが大きい css を無視できると、当然レスポンスが良くなる。
IE7/IE8 に対応している jquery-ui の css など。