2007年12月27日木曜日

[他]GUIのデザイン

この前TVを見ていると、医療事故のニュースをやっていました。
何でもその事件は、患者の疾患に対してありえない薬を投与したため、起こった事件のようでした。
で、事故の原因を探っていくと、

  • 医師がシステムに誤ってありえない薬を入力した
  • その薬が看護婦によってそのまま患者に投与された

ということでした。で、専門家の人?がTVで「そんなありえないような組み合わせはシステムでチェックして事故を防ぐんだよ!!のようなことを言っていました。

確かに一理あります。以前あった株取引の単価と数量の入れ間違えによる事件も同じようなことが原因で起こっています。しかし、果たしてシステム対応だけでこういった問題は解決するといえるのでしょうか?この論理であれば、システムを導入するまでは事故が起こらなかったことになります。

仮にこのようなチェックを実施するようにすると、以下のような現象が起こります。

  • 本当に間違ったような数値が入れられなくなる
  • エラーメッセージ表示により操作性が悪くなる(と思われる)
  • エラーに慣れてしまい、無意識にOKボタンを押下するようになる
  • (少なからず)開発コストが上昇する

結局最後は、人間がチェックするそれしかないのだと私は考えます。もちろん人に優しいシステム構築というのはソフト開発する上では永遠のテーマだと思いますが。。。

2007年12月21日金曜日

[他]文字列の解析について(by @it)

時々以下のようなデータをMapに復元するという処理を作ることがあります。

「key=value,aaa=bbb,ccc=123」

コレをMapに復元し、キーに「aaa」を指定すると「bbb」が取得できる。まぁちょっとしたシリアライズですね。
そんな話題が@itの掲示板(java)に出ていました。
この手の処理を作るときに、方法としては以下の2種類を思いつきます。

  1. 正規表現でスマートに処理
  2. ガチでパーサを実装する
  3. 1と2のいいところどり

こういうときに私の場合「2.」で実装しちゃいます。理由は以下の2点。

  1. メンテできないと言い出す人がいる(正規表現を理解できない人結構いる)
  2. 仕様変更時に正規表現が複雑になる場合がある、または正規表現では困難

ありがちな問題として、「=」や「,」をデータに含めたい。その対策として、値を「"値"」のようにしたい。などと言れることがあるからです。

とはいえ、いまどきの.NETであればXMLSerializerでこの手の問題に発展することはないんですけどね。

2007年12月20日木曜日

[.NET]MailSlot(その2)

昨日のMailSlotの受信について書きましたが、今日は送信側。コード例は以下のとおりです。
昨日のとあわせてクラス化してみました。
(実は半分くらいMOFさんからのぱくり品)

public class MailSlot
{
    #region Win32 API宣言

    [DllImport("kernel32.dll"CharSet = CharSet.Auto)]
    private static extern SafeFileHandle CreateFile(
        string lpFileName// ファイル名 
        DesiredAccess dwDesiredAccess// アクセスモード 
        ShareMode dwShareMode// 共有モード 
        int lpSecurityAttributes// セキュリティ記述子 
        CreationDisposition dwCreationDisposition// 作成方法 
        FlagsAndAttributes dwFlagsAndAttributes// ファイル属性 
        IntPtr hTemplateFile // テンプレートファイルのハンドル 
        );

    [DllImport("kernel32.dll"CharSet = CharSet.Auto)]
    static extern SafeFileHandle CreateMailslot(
        string lpName,
        uint nMaxMessageSize,
        uint lReadTimeout,
        IntPtr lpSecurityAttributes);

    #endregion

    #region 列挙体

    private enum DesiredAccess : uint
    {
        GENERIC_READ = 0x80000000,
        GENERIC_WRITE = 0x40000000,
        GENERIC_EXECUTE = 0x20000000
    }

    private enum ShareMode : uint
    {
        FILE_SHARE_READ = 0x00000001,
        FILE_SHARE_WRITE = 0x00000002,
        FILE_SHARE_DELETE = 0x00000004
    }

    private enum CreationDisposition : uint
    {
        CREATE_NEW = 1,
        CREATE_ALWAYS = 2,
        OPEN_EXISTING = 3,
        OPEN_ALWAYS = 4,
        TRUNCATE_EXISTING = 5
    }

    private enum FlagsAndAttributes : uint
    {
        FILE_ATTRIBUTE_ARCHIVE = 0x00000020,
        FILE_ATTRIBUTE_ENCRYPTED = 0x00004000,
        FILE_ATTRIBUTE_HIDDEN = 0x00000002,
        FILE_ATTRIBUTE_NORMAL = 0x00000080,
        FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000,
        FILE_ATTRIBUTE_OFFLINE = 0x00001000,
        FILE_ATTRIBUTE_READONLY = 0x00000001,
        FILE_ATTRIBUTE_SYSTEM = 0x00000004,
        FILE_ATTRIBUTE_TEMPORARY = 0x00000100
    }

    #endregion

    private string slot = string.Empty;
    public string Slot
    {
        get { return slot; }
        set { slot = value; }
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public MailSlot()
    {
    }

    /// <summary>
    /// メールスロット名付コンストラクタ
    /// </summary>
    /// <param name="strSlot">メールスロット</param>
    public MailSlot(string strSlot)
        : this()
    {
        Slot = strSlot;
    }

    /// <summary>
    /// メッセージの送信
    /// </summary>
    /// <param name="strMessage">送信する文字列</param>
    public void WriteMailSlot(string strMessage)
    {

        SafeFileHandle fileHandle = null;

        try
        {
            fileHandle = CreateFile(Slot,
                    DesiredAccess.GENERIC_READ | DesiredAccess.GENERIC_WRITE,
                    ShareMode.FILE_SHARE_READ | ShareMode.FILE_SHARE_WRITE,
                    0,
                    CreationDisposition.OPEN_EXISTING,
                    FlagsAndAttributes.FILE_ATTRIBUTE_NORMAL,
                    (IntPtr)0);


            using (FileStream fs = new FileStream(fileHandleFileAccess.Write))
            {
                byte[] msg = Encoding.UTF8.GetBytes(strMessage);
                fs.Write(msg0msg.Length);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            System.Diagnostics.Debug.WriteLine(ex.StackTrace);
            throw ex;
        }
    }

    /// <summary>
    /// メールスロット受信待ち
    /// </summary>
    /// <param name="maxMessageSize">最大メッセージ数</param>
    /// <param name="readTimeout">タイムアウト</param>
    public string ReadMailSlot(uint maxMessageSizeuint readTimeout)
    {
        string result = string.Empty;
        SafeFileHandle mailslotHandle = null;
        try
        {
            mailslotHandle = CreateMailslot(SlotmaxMessageSizereadTimeout, (IntPtr)0);

            using (FileStream fs = new FileStream(mailslotHandleFileAccess.Read))
            {
                byte[] buf = new byte[maxMessageSize];
                int len = fs.Read(buf0buf.Length);
                result = Encoding.UTF8.GetString(buf0len);

                fs.Close();
           }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            System.Diagnostics.Debug.WriteLine(ex.StackTrace);
            throw ex;
        }
        return result;
    }
}

[.NET]MailSlot

最近、過去の資産(今となってはレガシーな物)のリプレースをやっています。
で、そのシステムのなでMailSlotを使った通信をしていました。そこで送信・受信のドライバを作ろうと思って.NET Frameworkのライブラリを探したのですが、それらしいものが見つからず、仕方なく自作しました。
とりあえず、受信まちの場合、以下のようなコードで実現可能です。送信側はまた後日。

通常、CreateMailslotしたハンドルに対してCloseHandleする必要があるのですが、FileStream.close()がそれらしく処理してくれるっぽい。

[DllImport("kernel32.dll"CharSet = CharSet.Auto)]
static extern SafeFileHandle CreateMailslot(
    string lpName,
    uint nMaxMessageSize,
    uint lReadTimeout,
    IntPtr lpSecurityAttributes);

/// <summary>
/// メールスロット受信待ち
/// </summary>
/// <param name="maxMessageSize">最大メッセージ数</param>
/// <param name="readTimeout">タイムアウト</param>
public string ReadMailSlot(string slotuint maxMessageSizeuint readTimeout)
{
    string result = string.Empty;
    SafeFileHandle mailslotHandle = null;
    try
    {
        mailslotHandle = CreateMailslot(slotmaxMessageSizereadTimeout, (IntPtr)0);

        using (FileStream fs = new FileStream(mailslotHandleFileAccess.Read))
        {
            byte[] buf = new byte[maxMessageSize];
            int len = fs.Read(buf0buf.Length);
            result = Encoding.UTF8.GetString(buf0len);

            fs.Close();
        }

    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.Message);
        System.Diagnostics.Debug.WriteLine(ex.StackTrace);
        throw ex;
    }
    return result;
}


検索で結構ヒットしているようなので、その後のblogへリンクを追加

GDD Blog: [.NET]MailSlot(その2)
GDD Blog: [.NET]MailSlot(その3)

2007年12月10日月曜日

[他]HTTPのダウンロード(HTML)

ASP.NETでexcelファイルをダウンロードする場合、対象がIEの場合、何もしなければexcelがoleでIEの中に表示されているため、以下のようにContentTypeを指定して強制的にファイルをダウンロードします。

Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition""attachment; filename =target.xls");
Response.WriteFile("c:/path/target.xls");
Response.Flush();
Response.End();


しかし、@ITを見ていると、HTMLで解決できる方法が紹介されていました。以下のようになります。

<a href="/path/target.xls" type="application/octet-stream>ダウンロード</a>


・・・目からうろこでした。

2007年12月8日土曜日

[.NET]SVNクライアント(SubCommander)

Windowsの場合、SVNクライアントといえばTortoiseSVNが有名です。その他にもいくつかありますが、最近SubcommanderというSVNクライアントを発見しました。

TortoiseSVNはExplorerに統合されます。この点が賛否両論で分かれるところではあります。私は別に気になりませんが、気にする人は気にするみたいです。
で、Subcommanderは日本語化は出来ませんが、単独のアプリになります。何がよいかというと、複数のリポジトリをまとめて管理できるところです。ただ、まだ荒削りですが、私は期待しています。

現在携わっているプロジェクトでは、アセンブリ単位でリポジトリを分ける必要があり、ちょっとメンドクサイのでちょうどいいのです。ただ、VS.NETを使っている分ではAnkhSVNがあるので通常の操作では苦にはなりませんが、VS.NETを使わずにメンテしたい場合には最適ですね。

2007年12月6日木曜日

[他]SVNのホスティングサービス(無料)

時々何かを調べるために、簡単なテストプログラムを書くことがあります。その資産は以外に有用なものになります。
この小さなテストプログラムの数がある程度たまってくると、それを元に小さなテストプログラムを作ることができるので、作業効率が上がります。

しかし、家や会社あるいはマシン単位でばらばらに管理しているとどれが最新版かわからなくなったり、マシンによっては無かったりと、管理が面倒なことになります。で、それを解決するのがWeb上にあるファイル共有サービスだったりします。

しかし、ファイル単位で版数の管理をしたりとなるとCVSやSVNのような履歴を管理するシステムが必要となり、そういったサービスを提供してくくれる業者はなかなかありません。さらに無料ともなるとその数は激減します。

なかなか私の希望にかなうもの(無料・容量>50MB・オープン)はみつからず、数年の日々がたちました。

で、最近Google codeにSVNのホスティングサービスがあることを知りちょっと調べてみました。
・・・私の希望を希望を完全に満たしているうえに管理ツールまであります。登録も意外と簡単にすみ、
今はSVNを使って快適に過ごしています。
しかし、ある程度オープンなライセンスになってしまいます。なので仕事の情報が混じっていないかなど、ちょっと気をつけないといけませんね。

.NETのテストプログラムは数が少ないので完全に移行できましたが、Javaは整理にちょっと時間がかかりそうです。

2007年12月2日日曜日

[.NET]型付DataSet

VS.NET2005+.NET Framework2.0の環境で、データモデルを構築する場合、型付DataSetを使うのがもっとも手っ取り早く、障害も少なくシステムを構築することが出来ます。

しかし、
  • 型付DataSetをつくるまでもない
  • 流動的にDataTableの構成が変わる

のような場合、DBを使うシステムを前提としますが、DbDataAdapter.FillSchemaを使う方法があります。
(オーバーロードがいくつかあるのでこの限りではないかもしれませんが。。。)

このFillSchemaは、SELECT文にあわせてDataTableまたはDataSetを作ってくれます。
型付DataSetのようにコンパイルで障害の検出をすることは出来ませんし、都度DB接続するため、Webシステムでアクセス頻度の高いページには不向きかも知れませんが、動的に構成が変わる場合などにDataTable.Colmns.Addをちまちまやらなくても済みます。

2007年11月30日金曜日

[.NET]ComboBoxのDataSource

今日、お隣さんが独自クラスの配列をコンボボックスに表示しようとしていました。で「ComboBoxのDataSourceに適当なクラスの配列突っ込めないかなぁ?」といっていました。で私が「いやーダメでしょう」と言っている横で、DataSourceにその配列を突っ込み、DisplayMemberとValueMemberにその独自クラスのプロパティ名をセットしてデバッグを開始していました。
まぁ当然Bindするときにエラーになるだろうと踏んでいたんですが、なんと正しく動作しているではないですか。
2人して「うそだろー」とハモってしまいました。

で、気になってしまいMSDNに書いてあるComboBoxの説明を見ると以下のように記載されていました。
-----
■DataSource
IList インターフェイスを実装する、DataSet または Array などのオブジェクト。既定値は null 参照 (Visual Basic では Nothing) です。

■DisplayMember
DataSource プロパティで指定されたコレクションに格納されているオブジェクト プロパティの名前を指定する String。既定値は空の文字列 ("") です。

■ValueMember
DataSource プロパティで指定されたコレクションに格納されているオブジェクト プロパティの名前を表す String。既定値は空の文字列 ("") です。
-----

・・・いやーどうなってるんだろう。今日の発見にまったくつながらない。。。

しかしこの手の部類(ListBoxやDataGridVIew)も同じ方式で実はいけてしまう?様な気がします。機会があれば調べてみよう。と思いました。

2007年11月29日木曜日

[.NET]WindowsFormでのRadioButton

WebFormでもWindowsFormでもRadioButtonはグループ化し、そのグループの中で1つを選択できる仕様です。
WebFormの場合、RadioButtonListがありその中で選択しているのはどれか?というのが簡単にわかります。RadioButtonListを使わなくても、HTML的に選択されている項目が単一でわかるようになっています。

じゃあWindowsFormは?というと、一般的にWindowsFormの場合は、GroupBoxを利用(多分)して単一選択を可能にします。でもGroupBoxからは、どの項目が選択されているかはわからないようです。

単純にやるのであれば、チェックのイベントを拾って処理するってことになります。でもOK押下でDBに登録などといった動作は通常の使用としてありうると考えられます。
はて、WebFormのように簡単にやる方法って無いだろうか。。。今度調べてみよう。

2007年11月27日火曜日

[.NET]カレントディレクトリがかわってしまう?

.NETのアプリケーションは、起動したディレクトリをカレントディレクトリとします。
たとえば、アプリケーションが起動したディレクトリにあるhoge.xmlを読み込む場合、以下のようなコードを実装します。

byte[] buf = null;
using(FileStream fs = new FileStream(@".\test.txt"FileMode.OpenFileAccess.Read)){
    buf = new byte[fs.Length];
    fs.Read(buf0buf.Length);
    fs.Close();
}


このように「.\」を指定または何も指定しなければカレントとなります。
しかし、SaveFileDialogにてファイルを保存すると、勝手にカレントが変わってしまうのです。たとえば何かしらのファイル保存処理でデスクトップにファイルを保存すると、カレントがデスクトップになります。よって「.\」もデスクトップになってしまいます。これは、OpenFileDialogを利用しても同様の現象が起こります。
コレを回避するためには、FileDialog.RestoreDirectoryをtrueにすることで回避でます。

・・・って何でデフォルトでfalseなの?

インタフェースを使う側からすると、普通に考えてカレントが変わらない仕様を想定してしまいます。
この仕様にたどり着くまでに半日費やしました。とほほ。

2007年11月20日火曜日

[.NET]カスタムコントロールに新しいイベントを

仕事中、こんな相談を受けました。要約すると以下のとおりです。

  1. TextBoxの拡張版のコントロールを作成
  2. リターンキーを独自で処理する
  3. リターンキー押下時に連携するコントロールの入力をチェックする
  4. エラーがあったらErrorProvider風のエラー処理をする
というものでした。しかし「4」の工程でエラーの表現を独自のもの(表現)にしたい場合もある。という話があったので、「あぁ、そのコントロールに新しいイベント実装したら?」とアドバイスしました。
イベントが設定されていたらソイツを呼び出し、そうじゃなかったらコントロール内で処理すりゃいいってことになります。

・・・とは言ったものの、はて?どうやって実装すればいいんだろう?説明した相手も、「じゃあどうやって?」っていうような顔をしていました。でその人は「ちょっと調べてみます」といって去っていきました。

私も気になって調べてみました。すると意外と簡単に実装できそうなことがわかりました。
以下、参考になりそうなサイトです。

でもこういう実装をした場合、VS.NET上どのようになるんだろう?プロパティみたいに連携しそうな予感が。。。ちょっと見てみたい。もうWPFの時代が目の前ですが、そのうちサンプルを作成してみようと思います。

2007年11月19日月曜日

[.NET]シリアライズ

C#でxml及びバイナリ形式のシリアライズについてこのblogに記載しました。
.NET Framworkでは以下のクラスを利用すれば簡単にシリアライズすることが出来ます。

  • System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
  • System.Xml.Serialization.System.Xml.Serialization

最近VC++の改修案件があり、ソースをなめていると、MFCのCArchiveに関するコードが出てきました。
いやー実に懐かしい感じがしました。10年ほど前、こんなコードを見た記憶がよみかえりました。
当時、意味がわからずじまいで現在に至りましたが、今はコード見た瞬間にどういう意味なのかが飲み込めました。
言語は移り変わりますが、こういった考え方というのは変わらないものなんですね。

シリアライズを簡単に行う仕組みって、もしかしてMFCのアレが原点なんだろうか?

2007年11月15日木曜日

[.NET]log4netのFileAppender

log4netのFileAppenderはログファイルを排他モードでオープンします。
今回同一のファイルに対して複数のプロセスから同時に同じログファイルを出力する必要があるので、仕方なくファイルオープン周りの処理をカスタマイズするために調査してみました。
いざ実装しようとすると、それらしいモードが選択出来そうなコードが。。。でマニュアルを調べてみると設定ファイルに記載すれば済みそうなことがわかりました。
log4net(1.2.x)では定義ファイルに以下の一行を入れれば共有モードになります。

<lockingmodel type="log4net.Appender.FileAppender+MinimalLock"/>


「FileAppender+MinimalLock」で「FileAppenderの内部クラスMinimalLock」って表現するっぽいです。ちょっと微妙。
今回はコードからマニュアルを見る方向に行きました。しかし英語のマニュアルなので、読むのがつらい。と思いがちですがほとんど実例が載っています。先に読むクセを付けないと。。。
しかしこのMinimalLock多数のプロセスからの出力は考慮されていない感じな実装に見えました。今はごまかせてるっぽいのでいいですが、折を見てプロセス間共有可能な実装にカスタマイズする予定です。System.Threading.Mutexあたりを利用すれば実現できそうな予感が。。。

2007年11月13日火曜日

[.NET]virtualと非virtual

最近の作業で、log4netのRollingFileAppenderの拡張をしました。
で、改修ポイントを調査し、該当のメソッドをoverwriteし実装を完了しました。
その後、テストすると思うように動作しない。そこでブレイクポイントで待っているとなぜかブレイクしない。調べてみると、RollingFileAppender(継承元クラス)の該当メソッドが呼び出されていることがわかりました。

該当のメソッドは非virtualメソッドであり、拡張したクラスから呼び出された場合は拡張したクラスの該当メソッドが呼び出されますが、RollingFileAppenderから該当メソッドを呼んだ場合はRollingFileAppenderの該当メソッドが呼ばれることがわかりました。
そうなんです。C#ではデフォルトでメソッド呼び出しは非virtualなのです。

もともと私はJavaからC#にはいった経緯があり、Javaと同じ(デフォルトvirtual)つもりで実装してしまっていました。
ここは難易度の高いところだったのでヒヤヒヤでしたが、他の方法で機能は実現できたのでよしとしておこう。

2007年11月9日金曜日

[.NET]ADO.NET(ODP.NET)

ADO.NETでは、今までバラバラだったDBに関係するクラスが共通のインタフェースを実装したことと、FactoryMethodによるインスタンス生成のラップにより、どんなデータベースにつないでいるかを意識する必要が無くなりました。実装はこんなかんじ。



DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
using (DbConnection con = factory.CreateConnection())
using (DbCommand cmd = factory.CreateCommand())
{
    con.ConnectionString = "User Id=scott; Password=tiger; Data Source=ora10g";
    con.Open();
    cmd.Connection = con;
    DbDataReader reader = cmd.ExecuteReader("SELECT * FROM TEST_TABLE");
        :
        :
        :
    reader.Dispose();
}


ところが、この機構を使った場合、ODP.NETの拡張機能が使えない(使いづらい)ことになってしまいます。まぁ共通I/Fなので当たり前といえば当たり前なんですが。。。で、何が痛いかというと、

  • OracleCommand.BindByNameプロパティが使えない(キャストすれば。。。)
  • OracleCommand.OracleDbTypeプロパティが使えない(キャストすれば。。。)

です。で、何が不便かというと、

  • BindByNameの影響で、bind変数「:USER_ID」としてもOleDB同様「?」相当
  • OracleDbTypeの影響で、CHAR型のbind変数は、同じサイズにPadLeftしないとヒットしない

という問題が発生します。
だからといってキャストするのはイヤだしOracleしか使わないと決まっているシステムであったため、今回は直接ODP.NETのクラスを生成することにしました。とはいえDispose漏れの考慮からラップクラスは用意しています。

しかし、OLE DBやSQLServerの場合はDbProviderFactoryでいけるはず。Oracle以外にあたったらこの方式が最適なんでしょう。SQLServerは何もしなくても「@USER_ID」でbind出来ちゃうんだろうか。出来るのであればなぜODP.NETはそうじゃないんだろう?


あぁODP.NETでもこの問題が解決できればなぁ。。。

2007年11月7日水曜日

[.NET]System.Diagnostics.StackTrace(結果まとめ)

いろいろ悩んだSystem.Diagnostics.StackTraceにの利用についてですが、元々は以下のようなものを作ろうとしていました。
  • ネームスペースの一部分をファイル名にしたXMLを読み込む
  • 指定キーよりXMLの要素を取り出す
  • ただし、呼び出し元から指定するのはキーのみ

最終的にリスクが大きいと考え採用を見送ったことは記載しましたが、仕様を再確認したところ「指定キー」は重複しないことがわかりました。結果、以下のような仕様になりました。
  • クラス初期化時に、xxx.exe.configに記載された特定のキーからXML一覧を作成
  • XMLの一覧を順次読み込む
  • 読み込んだデータを1件ずつDictionaryに格納する
  • 指定キーからアクセスされた情報をそのまま返却(エラーチェックもしない)

キーが重複しないので結果フラットな構成でよく、後は分けられたファイルをマージするという構成となりました。えぇまぁ非常にシンプルな仕様です。

仕様を純粋に実装しようとして遠回りしましたが、非常にスッキリしたものが出来上がったような気がします。いやー仕様をいろいろな人と会話してみものですね。最近1人だと行き詰る傾向が。。。

2007年11月6日火曜日

[他]作業の進め方について(いまさら)

ソフト開発では、作ったプログラムが思うように動作せず、何をすればよいのかわからなくなってしまったり、ある修正が別の問題を引き起こしてしまったり。。。単純ではありません。
昨日私もそのような目にあい、一日中イタチごっこのような修正を繰り返し、結局解決せず、帰宅しました。その日、入浴中に「なにが問題なんだろう」と物思いにふけっていました。明日は問題整理としてから作業に取り掛かろう。そう思いました。

で、今朝、30分ほど仕様検討&問題を整理実施し、1つづつ順を追って作業してみると2時間で問題が解決しました。

問題が発生したときというのは、なぜか力ずくで何とかしたくなります。それは時として遠回りとなり、いつまでたっても問題が解決しないという悪循環な状態に陥ります。そこには必ずといってよいくらいに問題を整理し仮説を立てる作業を実施していない自分があります。

・・・もうおっさんなので、そのあたりをわきまえて作業しないと。と強く感じました。

2007年11月5日月曜日

[.NET]System.Diagnostics.StackTrace(その2)

System.Diagnostics.StackTraceによるコールスタックをたどる方法ですが、パフォーマンスがひどく悪くなることがわかりました。
履歴をたどれば1つのクラスに出来るんですが、何かあった場合のリスクを考慮して、クラスを分けることにしました。今回のケースは急がばまわれの典型と思い込むことにしました。

ただ、コピペによるコーディングミスが多発しそうな予感がするのでデバッグモードのみ整合性をチェックするロジックを入れることにしました。

2007年11月3日土曜日

[.NET]System.Diagnostics.StackTrace

今、以下のようなクラスを作っています。
  • ネームスペースの一部分をファイル名にしたXMLを読み込む
  • 指定キーよりXMLの要素を取り出す
  • ただし、呼び出し元から指定するのはキーのみ
そこで、メソッドの呼び出し履歴をさかのぼるために、System.Diagnostics.StackTraceを使ってみました。
とりあえずデバックモードでそれらしく動作したので、一安心していたのですが、ヘルプを見るとリリースモードでは呼び出しが最適化され、一部のコールスタックが最適化されることがある旨記載があっため念のためリリースモードで試してみると案の定コールスタックの出方が異なることがわかりました。

そこで、#ifで、デバッグモードとの切り替えを実施してみましたが、本来通過しているメソッドの履歴がなくなっており(この辺が最適化だと思います)ちょっと困っています。
いくつかのアセンブリに分かれるのとファイルの単位=アセンブリの単位であるのと、このクラスはこのクラスのみのアセンブリになるため、
  • コールスタックをさかのぼる方式は同じ
  • このクラスがあるアセンブリ以外のアセンブリまでさかのぼる
  • そのネームスペースのファイルを参照する
  • 指定キーよりXMLの要素を取り出す
  • 値を返却する
という方式をとってみようと思います。が、うまくいくだろうか。。。
最悪全部のアセンブリ用にサブクラスを作り、初期化ルーチンのみ実装する方針になりそうなんですが、何かいい方法は無いだろうか。。。

2007年11月1日木曜日

[.NET]アプリケーションセッティングとユーザセッティングの保存

WindowsFormアプリケーションでは、設定ファイルとして「アプリ名.config」というファイルを使用することが出来ます。
この設定はアプリ固有のものとユーザ固有のものと分けることが出来ます。

今回あるちょっとしたツールでiniファイルやレジストリ的にデータの保存がしたくてこの機能を利用してみました。このツールでは「アプリ名.config」に保存したいなぁと思い、以下のような実装をしました。
  • 該当のプロパティはアプリケーションスコープ
  • あるボタンを押下するとxxx.Properties.Settings.Default["YYY"]に値を設定
  • xxx.Properties.Settings.Default.Save()にて保存
しかし、いくらSave()を実行しても設定は保存できませんでした。調べてみるとアプリケーションスコープでは情報を保存できないとのことで、とりあえずユーザスコープに変更し、保存してみるとなんとなく保存できました。しかし、どこに保存されているかがわからず、またアプリケーションスコープに戻してみました。

ここでおかしな現象が発生しました。なぜか実行時にこのプロパティにアクセスすると例外が発生するようになりました。原因がさっぱりわからない。その後3時間くらい右往左往していて、たまたまExceptionのInnerExceptionを見ると何かおかしなパス(C:\Documents and Settings\matsuo\Local Settings\Application Data\インストール時に設定した会社名?)の書いてある例外が。。。そのパスを見るとそこにはユーザ設定らしいファイルが存在しました。

そのファイルを消すと元通り正しく動作するようにまりました。よく見ると過去に作ったアプリのフォルダも存在していました。
その後、プロジェクトのプロパティ「設定」画面の「同期」ボタンを押下することによりクリアできことがわかりました。

・・・挙動不信な例外が発生したら、まずマニュアルを。次に「InnerException」を。次にGoogleで。教訓になりました。

2007年10月30日火曜日

[他]AnkhSVNのちょっとした裏技

プロジェクトによっては、log4netなど外部アセンブリの参照をすることがあります。
しかしこのファイルを管理しないと、開発者個々に適当バージョンを使ったりしがちで、ちょっと面倒です。そこで、SVNに登録しておけばよいのですが、登録しようとした場合、プロジェクトに登録すればSVN上で管理が可能ですが、編集するものでもないので、プロジェクトに追加するのもちょっと。と感じます。
そんなときに、以下のような手順で意識することなく管理することが出来ます。

  • TortoiseSVNで該当のファイルをSVNにコミットする(プロジェクトフォルダと同じレベルのフォルダにしておくと良いかも)
  • AnkhSVNの機能で、ソリューションをアップデートする

するとプロジェクトに入っていないファイルであるにもかかわらず、勝手にチェックアウトされます。
結果、アセンブリの参照関係がそのままほかの人たちにもコピーされます。

2007年10月29日月曜日

[.NET]コントロールやコンポーネントのデフォルト値

自分でカスタマイズしたコントロールやコンポーネントは、VS.NETのデザイナで利用することができます。ツールボックスに表示され、Formにドロップすることで利用することが可能で、その際、そのクラスのプロパティは、デザイナのプロパティとして表示・設定することが出来ます。

で、設定するとその値は、該当のFormのxxx.Designer.csなどに含まれるInitializeComponent()メソッドに設定されます。コードが自動生成されるイメージです。で、プロパティに[DefaultValue("")]のようなアトリビュートを付けると、その値は初期値となるため、設定したのが初期値の場合、プロパティ設定のコードが省略されます。

それの何がよいかというと、コントロールの初期値を変更するだけで、値を設定していないプロパティが一気に切り替わるところです。しかし設定できる値の条件があり、Cololr型などの場合、[DefaultValue(Cololr.Red)]のような書き方をすることが出来ません。
で調べてみると、DefaultValueには引数が2つ渡せるようで、[DefaultValue(typeof(Color),"Red")]のような書き方が出ることがわかりました。ちょっと感動です。

2007年10月27日土曜日

[他]Blogの使い方?

最近Blogをはじめたばかりのせいか、なかなかいい議題やネタが出てきません。
というか出てもBlog書くまでに忘れてしまうのです。
で、今日思いついたんですが、一気に公開まで持っていくのではなく、昼や夕方の休憩時間や通勤中にキーワードだけ入力しておく。で、時間がある夜や休日に執筆し、公開する。そんな使い方もありだなぁと思いました。

2007年10月25日木曜日

[他]SVN+VS.NET その3

AnkhSVNでのSVN操作機能ですが、ファイル名の変更する場合、SVNではリネームが可能なんですが、削除+新規ってことがわかりました。
とりあえず今日はそのままコミットしましたが、TortoiseSVN を使えばリネームが出来るので、今後はそれで。でもAnkhSVNのほで不整合が出ちゃうかも。。。

2007年10月24日水曜日

[.NET]自作コントロールのプロパティのシリアライズ(その2)

先日バイナリフォーマットでのプロパティ保存についてを記載しましたが、
バイナリフォーマットにするほどでもない場合、xxx.Designer.csにデザイナから直接値を設定する方法があります。というかむしろこちらのほうが簡単。

修正内容は、先日の「■とあるコンポーネントに集約されているクラス」から[Serializable]をはずすだけです。
VS.NET2005は、以下のようなコードを出力します。

・・・特に理由が無ければ[Serializable]は付けないほうがいいかも。。。

    //
    // dataGridValidator1
    //
    this.dataGridValidator1.DataGridView = null;
    requiredData1.Message = "こらー";
    requiredData1.Required = true;
    requiredData2.Message = "";
    requiredData2.Required = false;
    requiredData3.Message = "";
    requiredData3.Required = false;
    requiredData4.Message = "おいおい";
    requiredData4.Required = true;
    this.dataGridValidator1.Items = new DustCS004.RequiredData[] {
requiredData1,
requiredData2,
requiredData3,
requiredData4};

2007年10月23日火曜日

[.NET]自作コントロールのプロパティのシリアライズ(前提)

先日書きました、「[.NET]自作コントロールのプロパティのシリアライズ 」ですが、前提条件を何も書いていなかったので、簡単に記載します。

用途としては、DataGridView のように列単位で処理や属性を新たに追加したい場合を想定しています。
こういった場合、バイナリフォーマットでリソースに情報を保存したい場合、以下のように実装します。
  • クラスに[Serializable]アトリビュートの付与
  • バイナリフォーマットでシリアライズ可能
  • シリアライズオブジェクトから復元可能なコンストラクタの実装

以下、コード例です。


//-------------------------------------------------
//■とあるコンポーネント
//-------------------------------------------------
public class DataGridValidator : Component
{
    private RequiredData[] items = new RequiredData[0];
    public RequiredData[] Items
    {
        get { return items; }
        set { items = value; }
    }

    private DataGridView dataGridView = null;
    public DataGridView DataGridView
    {
        set { dataGridView = value; }
        get { return dataGridView; }
    }

    public DataGridValidator()
        : base()
    {
    }
}

//-------------------------------------------------
//■とあるコンポーネントに集約されているクラス
//-------------------------------------------------
[Serializable]
public class RequiredData : ISerializable
{
    private bool required = false;
    public bool Required
    {
        get { return required; }
        set { required = value; }
    }

    private string message = String.Empty;
    public string Message
    {
        get { return message; }
        set { message = value; }
    }

    public RequiredData()
    {
    }

    // シリアライズ用コンストラクタ
    public RequiredData(SerializationInfo infoStreamingContext context)
    {
        Required = info.GetBoolean("Required");
        Message = info.GetString("Message");
    }

    #region ISerializable メンバ

    void ISerializable.GetObjectData(SerializationInfo infoStreamingContext context)
    {
        info.AddValue("Required"Required);
        info.AddValue("Message"Message);
    }
    #endregion
}

2007年10月22日月曜日

[他]SVN+VS.NET その2

まだファイル共有でやっていますが、複数人でSVNの運用をはじめました。
やっているうちにちょっと使いづらい点が出てきたのでメモ。
  1. 表示が英語。(誰か日本語版のパッチつくてくれなかなぁ)
  2. file://の場合、漢字入りのディレクトがあるとエラーが発生する
  3. チェックアウトフォルダに漢字入りディレクトリを指定するとエラーが発生する
  4. ロック・アンロックの自動化が出来なさそう(やり方が不明なだけ?)
  5. ロック・アンロックのメニューが遠い

1,5は仕方がないとして、2,3は回避可能。しかし4に関しては。。。楽観的ロックと考えれば良いのですが、VSS慣れしている人には苦痛を感じるようです。たとえば編集を始めると勝手にロックをかけるみたいな機能があればいうことなしなんですが。。。ちなみに私は、ロック・コミット・アンロック運用より、コミット・ダメならマージ運用の方があっています。運用次第ですが、ソースのバッティングってそんなには発生しませんし、ロック運用の場合、ソースにぎられたまま休まれたりするとテンション下がっちゃいますからねぇ。。。

2007年10月19日金曜日

[.NET]自作コントロールのプロパティのシリアライズ

配列で且つとあるクラスの配列をコントロールのプロパティにする場合、そのクラスは、ISerializableをインプリメントする必要があります。そうすることにより、デザイナで設定したプロパティは、リソースのプロパティになります。VS.NET2005はこんなコードをはきます。

this.hoge1.Items = new xxx.yyy.HogeItem[] {
((xxx.yyy.HogeItem)(resources.GetObject("hoge1.Items"))),
((xxx.yyy.HogeItem)(resources.GetObject("hoge1.Items1"))),
((xxx.yyy.HogeItem)(resources.GetObject("hoge1.Items2")))};


リソースから取得した値をデシリアライズし、キャストしています。

ところがこのHogeItemに属性追加やアトリビュート変更をすると、この処理が失敗するらしく、デザイナの画面にエラーが表示時されてしまいます。(xxx.yyy.HogeItem[]をxxx.yyy.HogeItem[]にキャストできません。みたいなメッセージです。)
で、毎回プロパティを入力しなおしていたのですが、以下の手順で回避することが出来ます。

  1. VS.NETを再起動する。
  2. リビルドする
  3. VS.NETをもう一度再起動する。

同じような目にあっている人は試してみてください。なお、この現象は、プロジェクトをいくつかに分離していないと発生しないようです。

2007年10月18日木曜日

[他]SVN+VS.NET

今かかわっているプロジェクトも調査工程が終わりに近づき、メンバーも増えてきました。
ソース管理をしなきゃと。で管理方法なんですが、

  • VSSはVS.NETの上位エディションにしか入っていない
  • CVSはVS.NETのプラグインがあるが、あまり使い勝手がよくない(だいぶ古い情報)
そこで、SVNのプラグインを試してみました。AnkhSVNというツールなんですが、操作性がよく、今後ソース管理はこれで十分だと感じました。

ただDIFFツールをが文字化けが発生してしまうのが少々残念。内臓のDiffツールはエンコードさえ指定すればそれらしく動作するみたいなんですが、使い慣れたツールWinMergeとTortoiseMergeの両方を試してみました。TortoiseMergeに関しては起動すらしませんでした。

何かいい方法は無いだろうか。。。

2007年10月17日水曜日

[.NET]Windows Formコントロールの拡張

WindowsFormの場合、標準でついている共通系コントロールは、デザイナーでプロパティを設定することにより動作や挙動をかえることが出来ます。
しかし、入力チェックをする場合にコントロール1つひとつに対して、コントロールからフォーカスが消えるタイミングでエラーと表示したい場合、ぱっと思いつく方法は、全部のコントロールのサブクラスを作成することです。
しかし、コレは手間がかかり且つ面倒な作業になります、そこで、IExtenderProvider をインプリメントしたコントロールを作成すると、なんと、画面に配備したコントロールにプロパティを追加したりすることが出来ます。
ちょっとしたアスペクト的な動作になります。

私の場合、ErrorProviderのサブクラスを作成し、それにIExtenderProvider をインプリメントし、自動的に入力必須をチェックするコントロールを作成しました。システム共通でコレを使いまわすと意外と開発効率に貢献できそう。

ここにIExtenderProvider を実装したサンプルがあります。
http://msdn.microsoft.com/library/ja/cpguide/html/cpconwinformsextenderprovidersample.asp?frame=true

ここでは、コントロールに制御が移るタイミングで、そのコントロールに紐付けられたメッセージを表示するというものです。コードの例が載っていますが、実装は意外とローテクなんです。。。

まだほかにも応用可能なテクニックになります。たとえばコントロール間の依存関係の自動検証など。
しばらくこの話題に関する情報をつづっていこうと思います。

2007年10月16日火曜日

[他]Gネタ

技術ネタではありませんが、先々週からTVで放送されはじめました、起動戦士ガンダムOOですが、思いっきり見逃してしまいました。で、調べてみるとなんとGYAOでいつでも見れるではないですが!!

先々週・先週を見逃した方、GYAOに行けば見ることが出来ます。ちなみに第一話は今週末までなら見ることが出来ます。

[他]日誌を付けてみます

私は、ソフト開発の仕事をしています。ソフト開発は新しい技術が日進月歩で出てきます。
そんな技術的な話題を中心に、日々の仕事中でわかったことを、Blogにメモっていこうと思います。
あまりたいした情報は出せないかもしれませんが、このメモが世間の誰かしらの役に立てば幸いです。