.net frameworkに標準でついているロギングクラス群を使ってみた、作ってみた。

【設定ファイル】
アプリケーション構成ファイルを追加して、そこに設定を記述する。
ログ出力判定
  1. 出力レベルが、TraceSourceで指定されているSourceSwitchのログレベル以上であるか確認
  2. TraceSourceに指定されているListener毎に以下を確認
    出力レベルが、Listenerのフィルタで指定されているログレベル以上であるか確認
  3. すべてパスしたListenerを使用してログ出力する。
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.diagnostics>
        <sources>
          <source name="Log" switchName="ss_info" switchType="System.Diagnostics.SourceSwitch">
            <listeners>
              <add name="console" />
              <add name="my_text" />
              <remove name="Default" />
            </listeners>
          </source>
        </sources>
        <switches>
          <add name="ss_info" value="Information" />
        </switches>
        <sharedListeners>
          <add name="console" type="System.Diagnostics.ConsoleTraceListener" initializeData="false">
            <filter type="System.Diagnostics.EventTypeFilter" initializeData="Warning"/>
          </add>
          <!-- 自前のTraceListenerのタイプは、"タイプフルネーム,アセンブリ名"と指定する。 -->
          <add name="my_text" type="TraceSourceSample.MyTraceListener,TraceSourceSample" initializeData="MyLogfile.log">
            <filter type="System.Diagnostics.EventTypeFilter" initializeData="Warning"/>
          </add>
        </sharedListeners>
      </system.diagnostics>
    </configuration>
    
    【カスタムListener】
    TextWriterTraceListenerがshift_jisで出力されて、フォーマットが気に入らなかったので作ってみた。
    (文字コード問題は、コンストラクタでStreamを渡してやればいいのだろうけど、
    フォーマットが変更できなそうだったので。)
    /// <summary>
    /// カスタムTraceLIstener
    /// </summary>
    /// <remarks>
    /// utf8なテキストファイルにログを出力する、スレッドセーフなTraceser
    /// </remarks>
    public class MyTraceListener : TraceListener
    {
        private static FileStream fs = null;
        private static StreamWriter sw = null;
    
        public MyTraceListener(String log_file_path)
        {
            //Todo:複数パラメータをつけたときの設定ファイル記述方法がわからない。
            //     AutoFlushフラグなど付けたりしたい。
    
            if (fs == null)
            {
                fs = new FileStream(log_file_path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                sw = new StreamWriter(fs, Encoding.UTF8);
            }
        }
    
        private String getDate()
        {
            return "[" + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "]";
        }
    
        public override void Write(string message)
        {
            /*
             * TraceSourceクラスのTraceEventメソッドを呼び出した際のログ出力処理は
             * 二回に分けて出力される。
             * 
             * 1, TraceEventTypeとイベントId -> Writeメソッドで出力
             * 2, ログメッセージ -> WriteLineメソッドで出力
             * 
             * その為、WriteLineメソッドだけメッセージの後ろに日付を追加している。
             */
    
            lock (sw)
            {
                sw.Write(message);
                sw.Flush();
            }
        }
    
        public override void WriteLine(string message)
        {
            lock (sw)
            {
                sw.WriteLine(message + getDate());
                sw.Flush();
            }
        }
    
        public override void Flush()
        {
            lock (sw)
            {
                sw.Flush();
            }
        }
    
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            sw.Dispose();
        }
    }
    
    【動作確認フォーム】
    public partial class Form1 : Form
    {
        private static readonly TraceSource ts = new TraceSource("Log");
    
        public Form1()
        {
            InitializeComponent();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
    
            try
            {
                for (int i = 0; i < 100; i++)
                {
                    Thread thd = new Thread(new ParameterizedThreadStart(trace));
                    thd.Start(i);
                }
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc.ToString());
            }
        }
    
        private void trace(Object param)
        {
            int idx = (int)param;
            ts.TraceEvent(TraceEventType.Warning, 0, "Event Message[" + idx.ToString() + "] ");
        }
    }
    

    進捗表示ダイアログ

    見た目

    メインフォーム

    public partial class MainForm : Form
    {
      public MainForm()
      {
        InitializeComponent();
      }
    
      private void btnSelectDir_Click(object sender, EventArgs e)
      {
        FolderBrowserDialog dialog = new FolderBrowserDialog();  //フォルダ選択ダイアログ
        if (dialog.ShowDialog() == DialogResult.OK)
        {
          txtPath.Text = dialog.SelectedPath;
        }
      }
    
      //非同期検索ボタンクリック
      private void btnSearch_Click(object sender, EventArgs e)
      {
        FileOperationProgressInfo pinfo = new FileOperationProgressInfo();
        FileSearchProcesser procer = new FileSearchProcesser();
    
        procer.proc(txtPath.Text, pinfo);
        ProgressDialog dialog = new ProgressDialog(pinfo);
        dialog.ShowDialog(this);
    
        if (pinfo.isError() == true)
        {
          MessageBox.Show(pinfo.getErrorInfo().ToString(), "エラー");
        }
      }
    
      //同期検索ボタンクリック
      private void btnSyncSearch_Click(object sender, EventArgs e)
      {
        FileSearchProcesser procer = new FileSearchProcesser();
    
        try
        {
          Cursor = Cursors.WaitCursor;
          procer.proc(txtPath.Text);
          MessageBox.Show("終了しました", "成功");
        }
        catch (Exception exc)
        {
          MessageBox.Show(exc.ToString(), "エラー");
        }
        finally
        {
          Cursor = Cursors.Arrow;
        }
      }
    }
    

    進捗情報クラス

    //進捗情報基本クラス
    public abstract class ProgressInfo
    {
      protected int total = 0;
      protected int count = 0;
      protected bool is_error = false;
      protected bool is_cancel = false;
      protected bool is_end = false;
      protected Exception exception = null;
      protected Object target = "";
    
      public ProgressInfo() { }
    
      public int getTotal() { return total; }
      public int getCount() { return count; }
      public bool isError() { return is_error; }
      public Exception getErrorInfo() { return exception; }
      public bool isCancel() { return is_cancel; }
      public bool isEnd() { return is_end; }
      public void cancel(){ this.is_cancel = this.is_end = true; }
    
      public abstract String getProcType();  //処理種類取得
      public abstract String getProcTargetName(); //処理対象取得
      public abstract String getProcDetailMessage();  //処理詳細メッセージ取得
    }
    
      //ファイル操作用ProgressInfo
      public class FileOperationProgressInfo : ProgressInfo
      {
        public enum ProcType
        {
          CREATE = 0,
          DELETE,
          COPY,
          MOVE,
          EDIT,
          SEARCH,
        }
    
        #region "操作名称Getter"
        private class ProcTypeNameGetter
        {
          protected int hashcode;
    
          public ProcTypeNameGetter() { }
    
          public virtual bool isTarget(int hashcode)
          {
            if (hashcode == this.hashcode)
              return true;
            else
              return false;
          }
    
          public virtual String getName() { return ""; }
        }
    
        private class DefaultGetter : ProcTypeNameGetter
        {
          public DefaultGetter() : base() { }
          public override bool isTarget(int hashcode){ return true; }
        }
    
        private class CreateNameGetter : ProcTypeNameGetter
        {
          public CreateNameGetter() : base() { hashcode = ProcType.CREATE.GetHashCode(); }
          public override string getName(){ return "作成"; }
        }
    
        private class DeleteNameGetter : ProcTypeNameGetter
        {
          public DeleteNameGetter() : base() { hashcode = ProcType.DELETE.GetHashCode(); }
          public override string getName() { return "削除"; }
        }
    
        private class CopyNameGetter : ProcTypeNameGetter
        {
          public CopyNameGetter() : base() { hashcode = ProcType.COPY.GetHashCode(); }
          public override string getName() { return "コピー"; }
        }
    
        private class MoveNameGetter : ProcTypeNameGetter
        {
          public MoveNameGetter() : base() { hashcode = ProcType.MOVE.GetHashCode(); }
          public override string getName() { return "移動"; }
        }
    
        private class EditNameGetter : ProcTypeNameGetter
        {
          public EditNameGetter() : base() { hashcode = ProcType.EDIT.GetHashCode(); }
          public override string getName() { return "編集"; }
        }
    
        private class SearchNameGetter : ProcTypeNameGetter
        {
          public SearchNameGetter() : base() { hashcode = ProcType.SEARCH.GetHashCode(); }
          public override string getName() { return "検索"; }
        }
        #endregion
    
        private ProcType type;
        private List<ProcTypeNameGetter> name_getters = new List<ProcTypeNameGetter>();
    
        public FileOperationProgressInfo() : base()
        {
          name_getters.Add(new CreateNameGetter());
          name_getters.Add(new DeleteNameGetter());
          name_getters.Add(new CopyNameGetter());
          name_getters.Add(new MoveNameGetter());
          name_getters.Add(new EditNameGetter());
          name_getters.Add(new SearchNameGetter());
          name_getters.Add(new ProcTypeNameGetter());  //最終要素には必ず一致するGetter
        }
    
        public void setInfo(int total, int count, String target, ProcType type, bool end)
        {
          this.total = total;
          this.count = count;
          this.type = type;
          this.is_end = end;
          this.target = target;
        }
    
        public override string getProcType()
        {
          for (int i = 0; i < name_getters.Count; i++)
          {
            if(name_getters[i].isTarget(type.GetHashCode()) == true)
            {
              return name_getters[i].getName();
            }
          }
    
          return ""; //実際には到達しない
        }
    
        public override string getProcTargetName()
        {
          return target.ToString();   
        }
    
        public override string getProcDetailMessage()
        {
          return getProcType() + "中: " + getProcTargetName();
        }
    
        internal void setErrorInfo(Exception exc)
        {
          this.is_error = true;
          this.is_end = true;
          this.exception = exc;
        }
      }
    

    進捗表示ダイアログ

    public partial class ProgressDialog : Form
    {
      private ProgressInfo pinfo;
    
      public ProgressDialog(ProgressInfo pinfo)
      {
        InitializeComponent();
    
        this.pinfo = pinfo;
        this.timer1.Enabled = false;
      }
      
      private void ProgressDialog_Load(object sender, EventArgs e)
      {
        /*
         * ロード時にタイマーを有効にする。
         * 進捗に変化が起こるまで(Totalが増える)待機する。
         */
        this.timer1.Interval = 50;
        this.timer1.Enabled = true;
    
        this.Owner.Cursor = Cursors.WaitCursor;
        while (pinfo.getTotal() <= 0)
          continue;
        this.Owner.Cursor = Cursors.Arrow;
      }
    
      private void timer1_Tick(object sender, EventArgs e)
      {
        /*
         * 50ms周期でタイマーイベントが動作する。
         * その際にProgressInfoから情報を取得、表示する。
         * また、処理がキャンセルされた場合は処理を中断する。
         */
        if (pinfo.isEnd() | pinfo.isCancel() | pinfo.isError() == true)
        {
          Close();
        }
    
        this.Text = pinfo.getProcType();
        progressBar1.Minimum = 0;
        progressBar1.Maximum = pinfo.getTotal();
        progressBar1.Value = pinfo.getCount();
        lblMsg.Text = pinfo.getProcDetailMessage();
      }
    
      private void btnCancel_Click(object sender, EventArgs e)
      {
        pinfo.cancel();
      }
    }
    

    スレッド間の同期取り

    下記処理で引っかかり中。デッドロックが回避できない気がしてきた。

    右のメインフォーム起動時にワークスレッドを起動させ、メッセージを出してからスレッドをロック。
    メインフォームのボタンクリック時のイベントで、ワークスレッドを再開させてからメインスレッドをロックする。
    この状態でワークスレッドが"再開したよ"ってメッセージを書きに行こうとするが
    メインスレッドがロックされているからデッドロックになってしまうと。。。。

    ソース

    public partial class MainForm : Form
    {
        private MessageForm sub_form;
        private Thread thread;
        private AutoResetEvent evt_thread;
        private AutoResetEvent evt_event;
    
        public MainForm()
        {
            InitializeComponent();
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            sub_form = new MessageForm();
            sub_form.Show();
    
            evt_thread = new AutoResetEvent(false);
            evt_event = new AutoResetEvent(false);
    
            thread = new Thread(work_thread);
            thread.Start();
        }
    
        private void work_thread()
        {
            dispText("[Thread] Waiting For PushEvent... ");
            evt_thread.WaitOne();
    
            /*
             * ワーカースレッドが再開したけども、メインスレッドはロックされたまま。
             * この状態でdispText()を行うと、デッドロックになる。。。
             */
            dispText("[Thread] Restart");
            evt_event.Set();
        }
    
        private void dispText(String msg)
        {
            sub_form.Invoke((MethodInvoker)delegate
            {
                sub_form.dispText(msg);
            });
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            dispText("[Event] Button Pushed");
    
            /*
             * このイベントはメインスレッドに属している。
             * その状態でウェイト処理でスレッドをロックする。
             */
            evt_event.Reset();
            evt_thread.Set();
            evt_event.WaitOne();
        }
    }
    

    等値演算子(==)とEqualsメソッド

    2つの値が等しいか調べる、等値演算子(==)とEqualsメソッドの違い: .NET Tips: C#, VB.NET

    処理

    参照型比較の場合はどちらの比較方法も、同じオブジェクトを参照しているか比較する。
    値型比較の場合はどちらの比較方法も、同じ値であるか比較する。

    早さ

    どちらもおなじくらいらしい。

    注意

    参照型クラスではEqualsメソッドをオーバーライドしても、等値演算子は規定の実装が存在するから
    オーバーロードするなとMSのガイドラインに記載されている。*1その為に
    ユーザー作成クラスで、Equalsメソッドでは値型比較を行い
    等値演算子では参照型比較を行うといった動きになる恐れがある。
    ちなみに、String型ではどちらの比較方法も値型比較を行う。

    どう使い分けるか

    値比較を行う場合は、Equalsメソッドを使用する。
    参照比較を行う場合は、等値演算子を使用する。

    *1:http://msdn.microsoft.com/ja-jp/library/7h9bszxx(VS.80).aspx

    UnitTest書いてみた

    リフレクションの勉強をかねてUnitTestツールを書いてみた。
    Formにはテスト一覧表示用のTreeViewとテスト結果出力用のTextBox、テスト実行ボタンButtonを配置する。
    CompareToメソッドをGetMethod()で取得する際、目的のクラスとObjectクラスにCompareToメソッドが存在するので注意すること。

    Formソース

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    using System.Reflection;
    using System.Threading;
    
    namespace TestUnitSample
    {
        /// <summary>
        /// テストフォーム
        /// </summary>
        public partial class TestRunnerForm : Form, ITestRunner
        {
            //Todo:ノードのチェックボックス初期値をtrueにする。
            //Todo:親にチェックがついたら、すべての子もチェックをつける。
            //Todo:すべての子のチェックがはずれたら、親のチェックも外す。
    
            public TestRunnerForm()
            {
                InitializeComponent();
    
                loadAssembly(new Assembly[] { this.GetType().Assembly });
            }
    
            //Runボタン押下
            private void button1_Click(object sender, EventArgs e)
            {
                run();
            }
    
            /// <summary>
            /// テスト対象アセンブリのロード
            /// </summary>
            /// <param name="assemblys"></param>
            public void loadAssembly(System.Reflection.Assembly[] assemblys)
            {
                //loop assemblys
                //  if assemblys[i] == UnitTestBase実装クラス
                //  then
                //    Node addNode = new Node
                //    addNode.Checked = false
                //    addnode.Tag = assemblys[i].Type
                //    loop assembly[0].GetType().GetMethods().Length
                //      if メソッド名が"test_"で始まっている
                //      then 
                //        Node childNode = new Node
                //        childNode.Checked = false
                //        childNode.Name = メソッド名
                //        childNode.Tag = MethodInfo
                //        addNodeの子にchildNode追加
                //      end
                //    end
                //    TreeViewにaddNodeを追加
                //  end
                //end
    
                for (int i = 0; i < assemblys.Length; i++)
                {
                    for (int j = 0; j < assemblys[i].GetTypes().Length; j++)
                    {
                        Type type = assemblys[i].GetTypes()[j];
    
                        if (type.BaseType != null && type.BaseType.Equals(typeof(UnitTestBase)))
                        {
                            TreeNode addNode = new TreeNode();
                            addNode.Checked = false;
                            addNode.Name = type.Name;
                            addNode.Text = type.Name;
                            addNode.Tag = type;
                            for (int k = 0; k < type.GetMethods().Length; k++)
                            {
                                MethodInfo mi = type.GetMethods()[k];
                                if (mi.Name.StartsWith("test_") == true)
                                {
                                    TreeNode childNode = new TreeNode();
                                    childNode.Checked = false;
                                    childNode.Name = mi.Name;
                                    childNode.Text = mi.Name;
                                    childNode.Tag = mi;
                                    addNode.Nodes.Add(childNode);
                                }
                            }
    
                            treeView1.Nodes.Add(addNode);
                        }
                    }
                }
            }
    
            /// <summary>
            /// テストの実行
            /// </summary>
            public void run()
            {
                //loop ノード数
                //  if node[i].Checked == true
                //  then
                //    obj = node[i].TagからType情報を取り出し、インスタンスを生成する。
                //    obj.initalize()
                //    loop node[i].Nodes.Length
                //      if node[i].Nodes[j].Checked == true 
                //      then
                //        スレッド作成。testThreadが実行されるように設定し、実行
                //      end
                //    end
                //    obj.end()
                //end
    
                String nl = System.Environment.NewLine;
    
                textBox1.Text += "Test Start. ==========================" + nl;
    
                for (int i = 0; i < treeView1.Nodes.Count; i++)
                {
                    if (treeView1.Nodes[i].Checked == true)
                    {
                        Type type = (Type)treeView1.Nodes[i].Tag;
                        UnitTestBase test_obj = (UnitTestBase)type.Assembly.CreateInstance(type.FullName);
                        test_obj.initalize();
    
                        for (int j = 0; j < treeView1.Nodes[i].Nodes.Count; j++)
                        {
                            if (treeView1.Nodes[i].Nodes[j].Checked == true)
                            {
                                MethodInfo mi = (MethodInfo)treeView1.Nodes[i].Nodes[j].Tag;
                                Thread thread = new Thread(new ParameterizedThreadStart(testThread));
                                thread.Start(new object[] { test_obj, mi });
    
                                while (thread.IsAlive == true)
                                    Application.DoEvents();
                            }
                        }
                    }
                }
            }
    
            //テスト実行
            private void testThread(object param)
            {
                //パラメタはobject[] 先頭要素にTestオブジェクト、次要素にMethodInfoが格納されている。
                //Object obj = object[0]
                //MethodInfo mi = object[1]
                //try
                //  obj.setup()
                //  mi.invoke(obj, null)
                //  obj.tearDown()
                //error
                //  ログTextBoxに例外詳細内容を表示
    
                object[] paramm = (object[])param;
                UnitTestBase test_obj = (UnitTestBase)paramm[0];
                MethodInfo mi = (MethodInfo)paramm[1];
                String nl = System.Environment.NewLine;
    
                try
                {
                    textBox1.Invoke((MethodInvoker)delegate{textBox1.Text += "[" + DateTime.Now.ToString() + "] " + mi.Name + " setup" + nl;});
                    test_obj.setup();
                    textBox1.Invoke((MethodInvoker)delegate { textBox1.Text += "[" + DateTime.Now.ToString() + "] " + mi.Name + " start test" + nl; });
                    mi.Invoke(test_obj, null);
                    textBox1.Invoke((MethodInvoker)delegate { textBox1.Text += "[" + DateTime.Now.ToString() + "] " + mi.Name + " end test" + nl; });
                }
                catch (Exception e)
                {
                    textBox1.Invoke((MethodInvoker)delegate
                    {
                        //textBox1.Text += "[" + DateTime.Now.ToString() + "]  " + e.InnerException + nl + e.StackTrace + nl;
                        textBox1.Text += "[" + DateTime.Now.ToString() + "]  " + e.InnerException + nl;
                    });
                }
                finally
                {
                    test_obj.tearDown();
                }
            }
        }
    
        /// <summary>
        /// TestRunnerインターフェイス
        /// </summary>
        public interface ITestRunner
        {
            /// <summary>
            /// テスト対象アセンブリのロード
            /// </summary>
            /// <param name="assemblys"></param>
            void loadAssembly(Assembly[] assemblys);
    
            /// <summary>
            /// テストの実行
            /// </summary>
            void run();
        }
    }
    

    UnitTestソース

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Reflection;
    
    namespace TestUnitSample
    {
        /// <summary>
        /// ユニットテスト基本クラス
        /// </summary>
        public class UnitTestBase
        {
            /// <summary>
            /// 初期化
            /// </summary>
            public virtual void initalize() { }
    
            /// <summary>
            /// 終了処理
            /// </summary>
            public virtual void end() { }
    
            /// <summary>
            /// テスト毎の初期化処理
            /// </summary>
            public virtual void setup() { }
    
            /// <summary>
            /// テスト毎の終了処理
            /// </summary>
            public virtual void tearDown() { }
    
            /// <summary>
            /// パラメタ値が等しいことを確認する
            /// </summary>
            /// <param name="val1">期待する値</param>
            /// <param name="val2">実際の値</param>
            /// <remarks>等しく無い場合、IFComparableインターフェイスを実装していない場合は例外が発生する。</remarks>
            public void assertEqual(object val1, object val2)
            {            
                //if 双方の値が同一型
                //then
                //  Type type = val1.GetType()
                //  if type.equals(val1, val2) == true
                //  then
                //    正常終了
                //  else
                //    例外投げる。エラーメッセージに 期待する値、実際の値を含める。
                //  end
                //else
                //  例外投げる。エラーメッセージに型が一致しなかったことを含める。
    
                if(val1.GetType().Equals(val2.GetType()))
                {
                    Type type = val1.GetType();
    
                    if (type.GetInterface("System.IComparable") != null)
                    {
                        MethodInfo mi = type.GetMethod("CompareTo", new Type[] { typeof(object) });
                        object result = mi.Invoke(val1, new object[] { val2 });
    
                        if ((int)result != 0)
                            throw new UnitTestAssertException("[assertEqual] expected value is " + val1 + " but "  + val2 + ".");
                    }
                    else
                    {
                        //IComparable実装クラスではない。
                        throw new UnitTestAssertException("[assertEquals] parameter object not implement IComparable interface.");
                    }
                }
    
            }
    
            /// <summary>
            /// パラメタ値がNullであることを確認する
            /// </summary>
            /// <param name="val1"></param>
            /// <remarks>Nullで無い場合は例外が発生する。</remarks>
            public void assertNull(object val1)
            {
                if (val1 != null)
                {
                    throw new UnitTestAssertException("[assertNull] parameter is not null.");
                }
            }
        }
    
        /// <summary>
        /// ユニットテスト実行エラー
        /// </summary>
        public class UnitTestAssertException : ApplicationException
        {
            /// <summary>
            /// コンストラクタ
            /// </summary>
            /// <param name="message"></param>
            public UnitTestAssertException(String message) : base(message) { }
        }
    
        /// <summary>
        /// テスト実装サンプルクラス
        /// </summary>
        public class SampleTest : UnitTestBase
        {
            private int val = -1;
    
            /// <summary>
            /// 初期化
            /// </summary>
            public override void initalize()
            {
                val = 0;
            }
    
            /// <summary>
            /// テスト毎の初期化処理
            /// </summary>
            public override void setup()
            {
                val = 0;
            }
    
            /// <summary>
            /// 足し算テスト
            /// </summary>
            public void test_addTest()
            {
                for (int i = 1; i <= 10; i++)
                    val += 0;
    
                assertEqual(100, val);
            }
    
            /// <summary>
            /// 引き算テスト
            /// </summary>
            public void test_subTest()
            {
                val = 10;
    
                for (int i = 1; i <= 10; i++)
                    val -= 1;
    
                assertEqual(0, val);
            }
        }
    }
    

    DataGridViewのVirtualModeサンプル

    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;
    
    namespace DataGridViewVirtualModeSample
    {
        public partial class Form1 : Form
        {
            private ArrayList record_datas = new ArrayList();  //グリッド表示データ
    
            /// <summary>
            /// ソーター基本クラス
            /// </summary>
            public abstract class RecSorter : IComparer
            {
                /// <summary>
                /// ソートオーダー
                /// </summary>
                protected SortOrder s_order = SortOrder.None;
    
                /// <summary>
                /// オーダーの設定
                /// </summary>
                /// <param name="order"></param>
                public void setOrder(SortOrder order) { s_order = order; }
                /// <summary>
                /// オーダーの取得
                /// </summary>
                /// <returns></returns>
                public SortOrder getOrder(){return s_order; }
    
                /// <summary>
                /// ソート
                /// </summary>
                /// <param name="x"></param>
                /// <param name="y"></param>
                /// <returns></returns>
                public abstract int Compare(object x, object y);
            }
    
            /// <summary>
            /// 文字列の比較
            /// </summary>
            public class StringRecSorter : RecSorter
            {
                private string col_name;  //列名称
                
                /// <summary>
                /// コンストラクタ
                /// </summary>
                /// <param name="colName"></param>
                public StringRecSorter(String colName) { col_name = colName; }
    
                /// <summary>
                /// 比較
                /// </summary>
                /// <param name="x"></param>
                /// <param name="y"></param>
                /// <returns></returns>
                public override int Compare(object x, object y)
                {
                    Hashtable xx = (Hashtable)x;
                    Hashtable yy = (Hashtable)y;
                    String x_val = (String)xx[col_name];
                    String y_val = (String)yy[col_name];
    
                    int sort_ret = x_val.CompareTo(y_val);
    
                    if (s_order == SortOrder.Descending)
                        sort_ret = sort_ret * -1;
    
                    return sort_ret;
                }
            }
    
            /// <summary>
            /// 浮動小数点列の比較
            /// </summary>
            public class DoubleRecSorter : RecSorter
            {
                private string col_name;  //列名称
                
                /// <summary>
                /// コンストラクタ
                /// </summary>
                /// <param name="colName"></param>
                public DoubleRecSorter(String colName) { col_name = colName; }
    
                /// <summary>
                /// 比較
                /// </summary>
                /// <param name="x"></param>
                /// <param name="y"></param>
                /// <returns></returns>
                public override int Compare(object x, object y)
                {
                    Hashtable xx = (Hashtable)x;
                    Hashtable yy = (Hashtable)y;
                    Double x_val = (Double)xx[col_name];
                    Double y_val = (Double)yy[col_name];
    
                    int sort_ret = 0;
    
                    if (x_val > y_val)
                        sort_ret = 1;
                    else if (x_val < y_val)
                        sort_ret = -1;
    
                    if (s_order == SortOrder.Descending)
                        sort_ret = sort_ret * -1;
    
                    return sort_ret;
                }
            }
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                dataGridView1.VirtualMode = true;
                dataGridView1.RowHeadersVisible = false;
                dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
    
                //
                // 外部定義ファイルにグリッド情報を定義しておくとより汎用的
                //
                DataGridViewColumn[] grid_cols = new DataGridViewColumn[3];
    
                grid_cols[0] = new DataGridViewTextBoxColumn();
                grid_cols[0].Name = "KindName";
                grid_cols[0].Tag = new StringRecSorter("KindName");
                grid_cols[0].ValueType = typeof(String);
                grid_cols[0].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
    
                grid_cols[1] = new DataGridViewTextBoxColumn();
                grid_cols[1].Name = "ItemName";
                grid_cols[1].Tag = new StringRecSorter("ItemName");
                grid_cols[1].ValueType = typeof(String);
                grid_cols[1].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
    
                grid_cols[2] = new DataGridViewTextBoxColumn();
                grid_cols[2].Name = "Price";
                grid_cols[2].Tag = new DoubleRecSorter("Price");
                grid_cols[2].ValueType = typeof(Double);
                grid_cols[2].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
    
                dataGridView1.Columns.AddRange(grid_cols);
            }
    
            //登録
            private void btnAddItem_Click(object sender, EventArgs e)
            {
                Hashtable row = new Hashtable();
                row["KindName"] = cmbItemKind.SelectedItem;
                row["ItemName"] = txtItem.Text;
                row["Price"] = txtPrice.Text;
                record_datas.Add(row);
    
                dataGridView1.RowCount += 1;
            }
    
            //セル値表示
            private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
            {
                try
                {
                    Hashtable row = (Hashtable)record_datas[e.RowIndex];
                    String col_name = dataGridView1.Columns[e.ColumnIndex].Name;
                    e.Value = row[col_name];
                }
                catch { }
            }
    
            //セル書式設定
            private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
            {
                if (e.RowIndex % 2 == 0)
                    e.CellStyle.BackColor = Color.Aqua;
            }
    
            //一括登録
            private void btnAddItems_Click(object sender, EventArgs e)
            {
                Random rmd = new Random(1000);
                String[] item_names = {"こんにゃく", "鉛筆", "カッター", "ぬいぐるみ", "きつね", "あぶらあげ"};
    
                for (int i = 0; i < 1000; i++)
                {
                    Hashtable row = new Hashtable();
                    int k_no = rmd.Next(6);
                    row["KindName"] = "区分_" + k_no.ToString();
                    k_no = rmd.Next(6);
                    row["ItemName"] = item_names[k_no] + "_" + i.ToString() ;
                    row["Price"] = 100.0d * k_no + i;
                    record_datas.Add(row);
    
                    dataGridView1.RowCount += 1;
                }
            }
    
            //ソート
            private void dataGridView1_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
            {
                if (e.Button == MouseButtons.Left)
                {
                    RecSorter sorter = (RecSorter)dataGridView1.Columns[e.ColumnIndex].Tag;
    
                    for (int i = 0; i < dataGridView1.ColumnCount; i++)
                        dataGridView1.Columns[i].HeaderCell.SortGlyphDirection = SortOrder.None;
    
                    if (sorter.getOrder() == SortOrder.Descending || sorter.getOrder() == SortOrder.None)
                    {
                        dataGridView1.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
                        sorter.setOrder(SortOrder.Ascending);
                    }
                    else
                    {
                        dataGridView1.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = SortOrder.Descending;
                        sorter.setOrder(SortOrder.Descending);
                    }
    
                    record_datas.Sort(sorter);
                    dataGridView1.Columns[e.ColumnIndex].Tag = sorter;
                    dataGridView1.Invalidate();
                }
            }
        }
    }