タイムアウトするメソッド(匿名メソッド版)

という事で、匿名メソッドを引数に取れるように書き直してみる。
前項の通り匿名メソッドを受け取るためにはシグネチャを固定しないといけないようなので、引数も戻り値もvoidのdelegateを作り、これを引数に取るようにしてみた。
こんな風にして使う。

TimedOperation.Invoke(
    TimeSpan.FromSeconds(waittime), 
    out aborted,
    delegate{
        obj = SomeMethod(exectime);
    });

または処理部分を直接書く。

TimedOperation.Invoke(
    TimeSpan.FromSeconds(waittime), 
    out aborted,
    delegate{
        System.Threading.Thread.Sleep(exectime * 1000);
        obj = input;
    });

変更したTimedOperationクラスのソースは以下の通り。
基本的にTimedDelegateというdelegateを定義してそれを使うように変えただけ。

public class TimedOperation
{
    public delegate void TimedDelegate();
    public static void Invoke(TimeSpan time, out bool aborted,
        TimedDelegate method)
    {
        if (method == null) throw new ArgumentNullException("method");
        if (time.TotalSeconds <= 0)
            throw new ArgumentOutOfRangeException("time");

        Operation op = new Operation();
        op.Method = method;

        Thread t = new Thread(new ThreadStart(op.Run));
        t.IsBackground = true;
        t.Start();

        if (!t.Join(time))
        {
            aborted = true;
            t.Abort(); // dangerous!
            return;
        }
        else
        {
            aborted = false;
            if (op.Exception != null) throw op.Exception;
            return;
        }
    }

    private class Operation
    {
        public volatile Delegate Method;
        public volatile Exception Exception;
        public volatile object Result;

        public void Run()
        {
            try
            {
                this.Result = Method.DynamicInvoke(new object[] { });
            }
            catch (ThreadAbortException) { throw; }
            catch (Exception exc)
            {
                this.Exception = exc;
            }
        }
    }
}

C#でクロージャの続き

いくつか実験してみた。
引数を取らないケース。これはOK

List<string> ls1 = new List<string>();
List<string> ls2 = ls1.FindAll(delegate
{
    return true;
});

引数に文字列を取るケース。これもOK

List ls1 = new List();
List ls2 = ls1.FindAll(delegate(string text)
{
    return true;
});

引数にintを取るケース。これは「'匿名メソッド' から 'System.Delegate' に変換できません。」と怒られる。

List<string> ls1 = new List<string>();
List<string> ls2 = ls1.FindAll(delegate(int i)
{
    return true;
});

List::FindAllもT型を1つ引数に取るシグネチャでないと匿名メソッドがコンパイル出来ないらしい。
Delagate型じゃなくて、特定のdelgateを仮引数に指定しないと、匿名メソッドを実引数に取れないようだ。

C#2.0でクロージャ

昨日、Rubyはシンプルに書けていいなぁと思ったら、タイムリーに
http://d.hatena.ne.jp/Kazzz/20050701/p2
経由で
http://joe.truemesh.com/blog//000390.html
を知った。
試しに昨日のソースを下記のように書き換えてみたら、

引数 '3': '匿名メソッド' から 'System.Delegate' に変換できません。

と言われた。
ちゃんと勉強しよう。

//メソッドの実行
/*
 *object obj = TimedOperation.Invoke(
 *   TimeSpan.FromSeconds(waittime), out aborted, 
 *   new MyDelegate(SomeMethod), new object[] { exectime });
 */
object obj = TimedOperation.Invoke(
    TimeSpan.FromSeconds(waittime), out aborted,
    delegate(int i)
    {
        System.Threading.Thread.Sleep(i * 1000);
        return i;
    }, new object[] { exectime }
    );

.NET Frameworkでメソッドをタイムアウトさせる方法

http://msdn.microsoft.com/msdnmag/issues/05/07/NETMatters/

MSDN Magazineにある、.NET関連のQ&Aみたいなページ。
今回扱ってるテーマは二つ。

  • Stringを変換してStreamにする方法
  • メソッドの実行を一定時間でタイムアウトさせる方法

タイムアウトの方が面白そうだったので、ちょっと試してみた。
下のページにあるタイムアウト用のクラスを使用する。Figure.2を参照。
http://msdn.microsoft.com/msdnmag/issues/05/07/NETMatters/default.aspx?fig=true#fig2
使い方はこんな感じになる。

using System;
using NetMatters;

namespace Console1
{
    class Program
    {
        delegate int MyDelegate(int input);
        static void Main(string[] args)
        {
            int exectime = 10;//メソッドの実行にかかる時間
            int waittime = 10;//タイムアウトするまでの許容時間
            if (args.Length > 1)
            {
                exectime = Convert.ToInt32(args[0]);
                waittime = Convert.ToInt32(args[1]);
            }
            Console.WriteLine("ExecTime {0:d}", exectime);
            Console.WriteLine("WaitTime {0:d}", waittime);
            bool aborted;//abortされたかどうか
            //メソッドの実行
            object obj = TimedOperation.Invoke(
                TimeSpan.FromSeconds(waittime), out aborted, 
                new MyDelegate(SomeMethod), new object[] { exectime });
            //結果
            Console.WriteLine(obj == null ? "Nothing Returned" : obj.ToString());
            Console.WriteLine(aborted ? "Aborted" : "Completed");
        }
        //時間制限付きで実行するメソッド
        static int SomeMethod(int input)
        {
            System.Threading.Thread.Sleep(input * 1000);
            return input;
        }
    }
}

実行結果は以下の通り。
それぞれ、時間内に終了するケースとタイムアウトするケース。

C:\VSS\VSSTEST\AWSTest\Console1\bin\Debug>console1.exe 1 5
ExecTime 1
WaitTime 5
1
Completed

C:\VSS\VSSTEST\AWSTest\Console1\bin\Debug>console1.exe 5 1
ExecTime 5
WaitTime 1
Nothing Returned
Aborted

C:\VSS\VSSTEST\AWSTest\Console1\bin\Debug>

Webサービスなんかで使えそうなのかな。

ちなみにRubyだと、

timeout(10) { 
    execSomeMethod(some args)
}

みたいに超簡単に書けるんだが、C#にそれを求めてはいけない。
プログラミング言語 Ruby リファレンスマニュアル

ASP.NET 2.0のGridView

ASP.NET 2.0で追加されたGridViewだが、気に入らない点が二つあった。

  • 編集と削除は出来るが新しいデータの挿入は出来ない。
  • DataSourceが0件(Empty)のとき、コントロール自体が表示されない。

これに対して、以下の機能を追加した。

  • Footerを使って新しいデータを挿入できるようにした。
  • EmptyTemplateを使って、データ0件の時は新規データの挿入だけ出来るようにした。

実装のポイント

  • FooterTemplateを使ってGridViewの下部にInsert用入力欄を追加した。
  • FooterTemplateを使うことでデフォルトのasp:BoundFieldが使えなくなった。
  • 表示用にItemTemplateを使った。DataSourceを結びつけるのにEvalを使った。
  • 編集用にEditItemTemplateを使った。DataSourceと結びつけるのにBindを使った。また、BindするためにはIDを付けないといけなかった。
  • FooterTemplateを使った影響で、編集、削除、更新、キャンセルのボタンも自動で追加できなくなった。asp:ButtonのCommandNameにそれぞれEdit, Delete, Update, Cancelを指定してボタンを作成した。
  • DataSourceのデータが0件(Empty)の時のために、EmptyDataTemplateを使ってデータ入力領域を作成した。
  • Insert処理はGridViewと同じDataSourceのInsert機能を使った。

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" Trace="false"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:GridView ID="GridView1" runat="server" AllowPaging="True"
            AutoGenerateColumns="False" CellPadding="4" DataKeyNames="column1" DataSourceID="SqlDataSource1"
            ForeColor="#333333" ShowFooter="True">
            <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
            <RowStyle BackColor="#EFF3FB" />
            <Columns>
                <%-- 1列目:ボタン用 --%>
                <asp:TemplateField HeaderText="">
                    <ItemTemplate>
                        <%-- 編集/削除ボタン --%>                    
                        <asp:Button CommandName="Edit" Text="Edit" runat="server" />
                        <asp:Button CommandName="Delete" Text="Delete" runat="server" />
                    </ItemTemplate>
                    <EditItemTemplate>
                        <%-- 更新/キャンセルボタン --%>                    
                        <asp:Button CommandName="Update" Text="Update" runat="server" />
                        <asp:Button CommandName="Cancel" Text="Cancel" runat="server" />
                    </EditItemTemplate>
                    <FooterTemplate>
                        <asp:Button ID="FooterInsert" runat="server" OnClick="Insert_Click" Text="Insert" />
                    </FooterTemplate>
                </asp:TemplateField>
                <%-- 2列目:Column1用 --%>
                <asp:TemplateField HeaderText="Column1">
                    <ItemTemplate>
                        <asp:Label ID="column1" Text='<%# Eval("column1") %>' runat="server" />
                    </ItemTemplate>
                    <EditItemTemplate>
                        <%-- Column1はキーなのでEdit時もLabelのまま。Evalのままで良い。 --%>                    
                        <asp:Label ID="column1" Text='<%# Eval("column1") %>' runat="server" />
                    </EditItemTemplate>
                    <FooterTemplate>
                        <asp:TextBox ID="TextBox1a" runat="server"></asp:TextBox>
                    </FooterTemplate>
                </asp:TemplateField>
                <%-- 3列目:Column2用 --%>
                <asp:TemplateField HeaderText="Column2">
                    <ItemTemplate>
                        <%-- 表示時はEvalでDataSourceと結びつける --%>                    
                        <asp:Label Text='<%#Eval("column2") %>' runat="server" />
                    </ItemTemplate>
                    <EditItemTemplate>
                        <%-- 更新時はBindでDataSourceと結びつける。IDも必要。 --%>                    
                        <asp:TextBox ID="column2" Text='<%# Bind("column2") %>' runat="server" />
                    </EditItemTemplate>
                    <FooterTemplate>
                        <asp:TextBox ID="TextBox2a" runat="server"></asp:TextBox>
                    </FooterTemplate>
                </asp:TemplateField>
                <%-- 4列目:Column3用 --%>
                <asp:TemplateField HeaderText="Collumn3">
                    <ItemTemplate>
                        <asp:Label Text='<%#Eval("column3") %>' runat="server" />
                    </ItemTemplate>
                    <EditItemTemplate>
                        <asp:TextBox ID="column3" Text='<%# Bind("column3") %>' runat="server" />
                    </EditItemTemplate>
                    <FooterTemplate>
                        <asp:TextBox ID="TextBox3a" runat="server"></asp:TextBox>
                    </FooterTemplate>
                </asp:TemplateField>
            </Columns>
            <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" />
            <%-- データ0件の時 --%>
            <EmptyDataTemplate>
                <table cellspacing="0" cellpadding="4" rules="all" border="1" style="color:#333333;border-collapse:collapse;">
                <tr style="color:White;background-color:#507CD1;font-weight:bold;">
                <th></th>
                <th>Column1</th>
                <th>Column2</th>
                <th>Column3</th>
                </tr>
                <tr style="background-color:#EFF3FB;">
                <td><asp:Button ID="EmptyInsert" runat="server" OnClick="Insert_Click" Text="Insert" /></td>
                <td><asp:TextBox ID="TextBox1" runat="server"></asp:TextBox></td>
                <td><asp:TextBox ID="TextBox2" runat="server"></asp:TextBox></td>
                <td><asp:TextBox ID="TextBox3" runat="server"></asp:TextBox></td>
                </tr>
                </table>
            </EmptyDataTemplate>
            <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" />
            <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
            <EditRowStyle BackColor="#D1DDF1" />
            <AlternatingRowStyle BackColor="White" />
        </asp:GridView>
        <%-- エラーメッセージ表示用 --%>
        <asp:Label ID="MsgBox1" runat="server" Text="Label"></asp:Label>
        <%-- データソース --%>        
        <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConflictDetection="OverwriteChanges"
            ConnectionString="<%$ ConnectionStrings:mydbConnectionString %>" 
            DeleteCommand="DELETE [Table1] WHERE [column1] = @original_column1"
            InsertCommand="INSERT INTO [Table1] ([column1], [column2], [column3]) VALUES (@column1, @column2, @column3)"
            SelectCommand="SELECT [column1], [column2], [column3] FROM [Table1]" 
            UpdateCommand="UPDATE [Table1] SET [column2] = @column2, [column3] = @column3 WHERE [column1] = @original_column1">
            <DeleteParameters>
                <%-- カラム名にoriginal_とPrefixが付くと更新前データを表すようだ。
                Deleteの時はoriginal_column1にしか値がないので、間違えてwhere句にcolumn1を指定していたらdeleteが空振りした。 --%>
                <asp:Parameter Name="original_column1" Type="String" />
            </DeleteParameters>
            <UpdateParameters>
                <asp:Parameter Name="original_column1" Type="String" />
                <asp:Parameter Name="column2" Type="String" />
                <asp:Parameter Name="column3" Type="String" />
            </UpdateParameters>
            <InsertParameters>
                <asp:Parameter Name="column1" Type="String" />
                <asp:Parameter Name="column2" Type="String" />
                <asp:Parameter Name="column3" Type="String" />
            </InsertParameters>
        </asp:SqlDataSource>
    
    </div>
    </form>
</body>
</html>

Default.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        //エラーメッセージをクリア
        MsgBox1.Text = "";
    }
    protected void Insert_Click(object sender, EventArgs e)
    {
        Control cnt = ((Control)sender).Parent;
        string id = ((Control)sender).ID;
        string id1 = "TextBox1";
        string id2 = "TextBox2";
        string id3 = "TextBox3";
        //Footerから呼ばれたときとEmptyDataTemplateから呼ばれたときの振り分け
        if (id.Equals("FooterInsert"))
        {
            id1 += "a";
            id2 += "a";
            id3 += "a";
        }
        string key = ((TextBox)cnt.FindControl(id1)).Text.Trim();
        if (key.Length == 0)
        {
            MsgBox1.Text = "column1は入力必須です。";
            return;
        }
        SqlDataSource1.InsertParameters["Column1"].DefaultValue = key;
        SqlDataSource1.InsertParameters["Column2"].DefaultValue = ((TextBox)cnt.FindControl(id2)).Text;
        SqlDataSource1.InsertParameters["Column3"].DefaultValue = ((TextBox)cnt.FindControl(id3)).Text;
        try
        {
            SqlDataSource1.Insert();
        }
        catch (System.Data.SqlClient.SqlException ex)
        {
            if (ex.Class == 14 && ex.Number == 2627)
            {
                MsgBox1.Text = "column1が重複しています。";
            }
            else
            {
                throw;
            }
        }
    }
}

.NET Frameworkで使われているデザインパターン

http://msdn.microsoft.com/msdnmag/issues/05/07/DesignPatterns/

.NET FrameworkASP.NETで使用されているデザインパターンの紹介。
クラスライブラリで実際に使われてる例を通じてデザインパターンを学ぶのは、良い学習方法だと思う。

挙がっているのは、

Observer
Windows Form
Iterator
C#のforeach構文の裏で動いてるIEnumerableやIEnumerator
Decorator
System.IO.Streamの辺り。FileInputStreamにBufferedStreamやCryptoStreamを被せてもStreamとして扱える、とか。
Adapter
COMと.NETのコンポーネントの相互利用。
Factory
System.Net.WebRequest.Create(URI)。URIからHttpWebRequestなどの適切なサブクラスをインスタンス化して返す。
Strategy
IComparer.Compareを使用したArray.Sort。大小比較の基準だけ変えてQuickSortが出来る。
Composite
ここからASP.NET。Web.UI.ControlはComposite Patternそのまま。
Template Method
Custom Web ControlでRenderだけOverrideするとか。あと、ASP.NETのPipeline。これもパターンなのか。
Intercepting Filter
HttpApplicationがEventに応じてIHttpModuleをキックするところ。こんな名前のパターンってGoFにあったっけ?
Page Controller
System.Web.UI.Page。これも良く分からん。

結構いっぱいあるな。

関連ページ:
http://www.atmarkit.co.jp/fdotnet/designptn/index/index.html
http://www.microsoft.com/japan/msdn/security/guidance/secmod37.mspx#EAAA

ASP.NET に熟練する: カスタム エンティティ クラスの概要

http://www.microsoft.com/japan/msdn/net/aspnet/CustEntCls.asp

AAfNで言うビジネスエンティティ部分について、DataSetでお手軽に実装するか、クラスをガチッと自作するかの問題。
自作する場合に実装するべきことや考慮するべきことが挙がっている。
以下、簡単に要約。

エンティティをどうやって持つかという問題。

DataSetに持つというのが一番お手軽な方針だが、いくつかの欠点がある。
1. エンティティがデータベース構造(列名や列順)に依存してしまう。
スキーマの変更が、アプリケーション層やビジネス層でエンティティを扱う場面にも影響してしまう。

2. 型指定が弱い。
全部Objectで返ってくるのでキャストやNullのチェックが必要。

3. オブジェクト指向でない。

カスタムエンティティクラスの使用

これに対する解決策がカスタムエンティティクラス。DataSetの代わりに専用クラスにまとめる。
メリット:継承、カプセル化が可能。メソッドの追加
デメリット:O/Rマッピングが必要。
⇒継承やリレーションシップの処理方法といった、いわゆるインピーダンスミスマッチの発生。
参考:デザインパターンを利用したDBアクセスの実装 (1/3):JavaのDBアクセスを極める(5) - @IT

複数のカスタムエンティティを扱う方法

ArrayList
メリット:簡単。
デメリット:厳密な型指定がない。カスタム動作を追加できない。

・カスタムコレクション
メリット:型指定。メソッドの追加。CollectionBaseを継承して作成していれば、簡単にバインドできる。
デメリット:実装が大変。

エンティティ間のリレーションの管理

DBは外部キーでマッピング。オブジェクトは参照の保持でマッピング

その他いろいろ