C# Notes

C# 関連のメモ。サマリーなど MSDN ライブラリ または MSDN:C# プログラミング ガイド より引用。

INDEX




TEXT

C# の機能
デリゲート
サマリー
.NET Framework 1.0, C# 1.0, Visual Studio 2002

デリゲートとは、「メソッドシグネチャー(署名、すなわちメソッドを一意に識別するためのもの)を定義(安全にカプセル化)する "型"」である。

同じシグネチャーを持つメソッドに、処理を任せる(委譲:デリゲート)というイメージ。イベントハンドラーは、デリゲートを使用して、ハンドリングするメソッドを特定している。

C++などの関数ポインターとほぼ同義であるが、タイプセーフであり、インスタンスメソッドの参照が可能、また、複数のメソッドを同時に参照することが可能、そしてオブジェクト指向である、という点が異なる。あととにかく安全。

デリゲートをインスタンス化すると、パラメーターや戻り値のやりとりは、すべてデリゲートを介して行われる。

簡単な実装の例
標準出力に引数の文字列を出力するだけのデリゲートメソッドを定義し、static メソッド、インスタンスメソッドで、順にデリゲートをインスタンス化し、呼び出している。実行結果を見ると、(代入した)順に(逐次)実行されることがわかる。

+= 演算子で代入出来ることから、-= 演算子で削除することも可能なことがわかる。

また、複数のメソッドを代入することを、"マルチキャストデリゲート" と呼ぶ。

using System;

namespace CsFeatures.Delegate
{
    // デリゲートを定義
    public delegate void Del( string message );

    public class DelegateTest
    {
        public static void Main()
        {
            // staticメソッド と インスタンスメソッド でデリゲートをインスタンス化
            MethodClass mc = new MethodClass();
            Del handler = MethodClass.StaticDelegateMethod;
            handler += mc.InstanceDelegateMethod;

            // デリゲートを呼び出す
            handler( "Hellow World" );
        }
    }

    public class MethodClass
    {
        // static メソッド
        public static void StaticDelegateMethod( string m )
        {
            Console.WriteLine( "static メソッドによる\t{0}", m );
        }
        // インスタンスメソッド
        public void InstanceDelegateMethod( string m )
        {
            Console.WriteLine( "インスタンス メソッドによる\t{0}", m );
        }
    }
}

実行結果
static メソッドによる Hellow World
インスタンス メソッドによる Hellow World

パラメーター
インスタンス化されたデリゲート(上記の例では handler)は、オブジェクトであるため、パラメーターとして引き渡すことができる。

下記の例では、デリゲートをパラメーターとして受け取り、それを呼び出している。

public static void Main()
{
    // デリゲートをインスタンス化
    Del handler = MethodClass.StaticDelegateMethod;

    // インスタンス化されたデリゲートを渡す
    MethodWithCallback( 1, 2, handler );
}

// デリゲートをパラメーターとして受けるメソッド
public static void MethodWithCallback( int i, int j, Del callback )
{
    // 受け取ったデリゲートを呼び出す
    callback( String.Format( "{0} + {1} = {2}", i, j, i + j ) );
}

実行結果
static メソッドによる 1 + 2 = 3

Predicate
このようにパラメーターで渡すことにより、たとえば、ある数値のパラメーターが "特定の条件" のときだけ処理をするというような場合、この "特定の条件" をデリゲートとして受け取ることができる。それにより、処理メソッドではその条件を関知する必要がなくなる。

この "特定の条件" を調べることを Predicate と呼ぶ。

下記の例では、デリゲートを代入する際の new キーワードを省いているが、これは C# 2.0 からの暗黙の型変換である。

public class DelegateTest
{
    // デリゲートの定義
    public delegate bool Predicate( int i );

    public static void Main()
    {
        // 数値の配列を定義
        var array = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        // 偶数のみ出力
        Console.WriteLine( "偶数-----" );
        WriteEvenNumber( array, IsEven );

        Console.WriteLine();

        // 4の倍数のみ出力
        Console.WriteLine( "4の倍数-----" );
        WriteEvenNumber( array, IsMultiplesOf4 );
    }

    // int 配列、デリゲートをパラメーターとして受け取り、
    // Predicate を満たす数値のみ、標準出力する。
    public static void WriteEvenNumber( int[] a, Predicate p )
    {
        foreach( var i in a )
        {
            if( p( i ) ) Console.WriteLine( i );
        }
    }
    // 偶数かどうかを調べるメソッド
    public static bool IsEven( int i ) { return ( i % 2 == 0 ); }
    // 4の倍数かどうかを調べるメソッド
    public static bool IsMultiplesOf4( int i )
    {
        return ( i % 4 == 0 ) && ( i != 0 );
    }
}

実行結果
偶数-----
0
2
4
6
8
10

4の倍数-----
4
8




ジェネリック
サマリー
.NET Framework 2.0, C# 2.0, Visual Studio 2005

ジェネリックとは、「多種多様な型に対応するために、パラメーターとして型を与え(型パラメーター)、クラスやメソッドを設計(定義)する機能」である。

インスタンス化されるまで、型の指定を遅延させるクラスやメソッドを設計(定義)できる。コレクションなどで、格納されているデータの型を "コレクション側で固定にしない"、"使用する側で型を指定" する。

ジェネリックを使用することによる利点
  • コンパイル時にタイプセーフな(型チェックを行ってくれる)コレクション(コンテナー)を作成できる
  • ArrayList などを使用する場合のボックス化、ボックス化解除によるパフォーマンスの改善
  • 様々な要素、方式、操作を最小限に抑え(まとめ)られるので、直交性が高い

ジェネリッククラス
ジェネリッククラスの例を以下に示す。至極単純なリンクリスト クラスを作っている。

using System;
using System.Collections.Generic;

namespace CsFeatures.Generics
{
    class GenericsTest
    {
        public static void Main()
        {
            var stringArray = new[] { "A", "B", "C", "D", "E" };
            var stringList = new GenericsList<string>();
            var intList = new GenericsList<int>();

            for( int i = 0; i < stringArray.Length; i++ )
            {
                stringList.Add( stringArray[ i ] );
                intList.Add( i );
            }

            Console.Write( "文字列配列" );
            foreach( string s in stringList ) Console.Write( "\t{0}", s );
            Console.WriteLine();

            Console.Write( "数値配列" );
            foreach( int i in intList ) Console.Write( "\t{0}", i );
            Console.WriteLine();
        }
    }

    public class GenericsList<T>
    {
        // 次のデータ next を持つノードクラス
        // このクラスでも T を使用できる。
        private class Node
        {
            private T data;
            public T Data
            {
                get { return this.data; }
                set { this.data = value; }
            }
            private Node next;
            public Node Next
            {
                get { return this.next; }
                set { this.next = value; }
            }

            // コンストラクター:この時点で、次のデータは null
            public Node( T t )
            {
                this.data = t;
                this.next = null;
            }
        }

        // ノード
        private Node head;
        public GenericsList()
        {
            this.head = null;
        }

        // ノードを追加
        // Next にカレントデータを、カレントデータに Add するデータを格納する
        public void Add( T t )
        {
            Node node = new Node( t );
            node.Next = this.head;
            this.head = node;
        }

        // foreach で取得される IEnumerator
        public IEnumerator<T> GetEnumerator()
        {
            Node current = this.head;

            while( current != null )
            {
                // カレントのデータを返却
                yield return current.Data;

                // Next に移動
                current = current.Next;
            }
        }
    }
}

実行結果
文字列配列 E D C B A
数値配列 4 3 2 1 0

ジェネリックメソッド
ジェネリックメソッドの例を以下に示す。2 つの値を swap(交換) する。

ジェネリックを使用しない場合、int 用 swap、string 用 swap を定義しなければならない(コーディングのオーバーヘッド)が、ジェネリックを使用することにより、これを解決する。

using System;
using System.Collections.Generic;

namespace CsFeatures.Generics
{
    class GenericsTest
    {
        public static void Main()
        {
            int a = 1, b = 2;
            string x = "A", y = "B";
            Console.WriteLine( "Swap 前\ta={0} b={1}, x={2} y={3}"
                , a, b, x, y );
            Swap<int>( ref a, ref b );
            Swap( ref x, ref y );
            Console.WriteLine( "Swap 後\ta={0} b={1}, x={2} y={3}"
                , a, b, x, y );
        }

        // 指定した 2 つのパラメーターをスワップ(交換)する。
        public static void Swap<T>( ref T l, ref T r )
        {
            T w = l;
            l = r;
            r = w;
        }
    }
}

実行結果
Swap 前 a=1 b=2, x=A y=B
Swap 後 a=2 b=1, x=B y=A

型パラメーターの制約
型パラメーターに制限を適用できる。

制限を適用したジェネリッククラス、メソッドを使用するとき、その制限を満たしていない場合、コンパイルエラーが発生する。この制限を "制約" と呼び、 where キーワードで指定する。

以下は、「型パラメーターは IComparable を実装する型に限る」、という制約を設けた Max メソッドのサンプル。SampleClass 型は IComparable を実装していないため、コンパイルエラーが発生する。

using System;
using System.Collections.Generic;

namespace CsFeatures.Generics
{
    class GenericsTest
    {
        public static void Main()
        {
            Console.WriteLine( "i : {0}", Max<int>( 10, 9 ) );
            Console.WriteLine( "i : {0}"
                , Max( Int32.MinValue, Int32.MaxValue ) );
            Console.WriteLine( "d : {0}", Max( 5.4, 5.5 ) );
            Console.WriteLine( "s : {0}", Max( "ABC", "DEF" ) );

            // 以下はコンパイルエラー
            //SampleClass x = new SampleClass(), y = new SampleClass();
            //Console.WriteLine( "sanpleclass : {0}", Max( x, y ) );
        }

        private class SampleClass { }

        // 2 つの値を比較し、大きいほうを返す
        // where ~ は、引数の型が IComparable を実装している型に限るという制限
        // これにより CompareTo メソッドを使用できる。
        public static Type Max<Type>( Type x, Type y )
            where Type : IComparable
        {
            return 0 < x.CompareTo( y ) ? x : y;
        }
    }
}

実行結果
i : 10
i : 2147483647
d : 5.5
s : DEF

制約の種類



イテレーター
サマリー
.NET Framework 2.0, C# 2.0, Visual Studio 2005

イテレーターとは、「foreach ステートメントで呼び出される "反復処理"」である。イテレーターブロックを用いてこれを実現する。

イテレーターのポイント
  • yield return ステートメントで各要素を返し、yield break で終了する。
  • 複数のイテレーターを 1 つのクラスに実装できる。その際、一意の名前を持つ必要がある。
    ex. foreach( int x in MyClass.Iterator2 ){ /*...*/ }
  • イテレーターの戻り値は、IEnumerable、IEnumerator、IEnumerable<T>、IEnumerator<T> のいずれかである必要がある。

非 ジェネリック
以下は、非ジェネリックのコード。単純に曜日名を列挙するクラス。

using System;
using System.Collections;
using System.Collections.Generic;

namespace CsFeatures.Iterator
{
    public class IteratorTest
    {
        public static void Main()
        {
            // 非ジェネリック曜日名クラス
            DayOfTheWeek week = new DayOfTheWeek();
            foreach( var d in week ) 
                Console.Write( d + " " );
        }

        // 非ジェネリック曜日名クラス
        private class DayOfTheWeek
        {
            // 曜日名定義
            string[] days = new[] 
                { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };

            // GetEnumerator メソッドを定義することにより、
            // 列挙型とみなされ、foreach ステートメントで使用できるようになる。
            public IEnumerator GetEnumerator()
            {
                for( int i = 0; i < this.days.Length; i++ )
                {
                    // 逐次返却する
                    yield return this.days[ i ];

                    // 金曜日で処理を終了
                    if( i == 5 ) yield break;
                }
            }
        }
    }
}

実行結果
Sun Mon Tue Wed Thr Fri

ジェネリック
以下はジェネリックのコード。Stack クラスを定義している。

using System;
using System.Collections;
using System.Collections.Generic;

namespace CsFeatures.Iterator
{
    public class GenericIteratorTest
    {
        public static void Main()
        {
            Stack<int> s = new Stack<int>( 10 );
            for( int i = 0; i < 10; i++ ) s.Push( i );

            string format = "{0} ";

            // ジェネリックイテレーター
            Console.Write( format, "ジェネリックイテレーター:" );
            foreach( var n in s )
                Console.Write( format, n );
            Console.WriteLine();

            // top to bottom 
            Console.Write( format, "top to bottom:" );
            foreach( var n in s.TopToBottom )
                Console.Write( format, n );
            Console.WriteLine();

            // bottom to top
            Console.Write( format, "bottom to top:" );
            foreach( var n in s.BottomToTop )
                Console.Write( format, n );
            Console.WriteLine();

            // Top N(5)
            Console.Write( format, "top 5:" );
            foreach( var n in s.TopN( 5 ) )
                Console.Write( format, n );
            Console.WriteLine();
        }

        // ジェネリック Stack クラス(後入れ先出し)
        private class Stack<T> : IEnumerable<T>
        {
            // 値
            private T[] values;

            // 最上位置
            private int top = 0;

            // コンストラクター
            public Stack( int max ) { this.values = new T[ max ]; }

            // Push
            public void Push( T t ) { this.values[ this.top++ ] = t; }

            // Pop
            public T Pop() { return this.values[ --this.top ]; }

            // ジェネリックイテレーター
            public IEnumerator<T> GetEnumerator()
            {
                for( int i = this.top - 1; 0 <= i; i-- )
                {
                    yield return this.values[ i ];
                }
            }

            // 非ジェネリック (IEnumerable) イテレーター
            // IEnumerable<T> は IEnumerable を継承するため、
            // IEnumerable.GetEnumerator メソッドを実装しなければならない。
            IEnumerator IEnumerable.GetEnumerator()
            {
                // ジェネリックイテレーター を返す
                return GetEnumerator();
            }

            // top to bottom イテレーター
            public IEnumerable<T> TopToBottom
            {
                // this を返すことにより、GetEnumerator メソッドが呼び出される
                get { return this; }
            }

            // bottom to top イテレーター
            public IEnumerable<T> BottomToTop
            {
                get
                {
                    for( int i = 0; i < this.top; i++ )
                        yield return this.values[ i ];
                }
            }

            // Top N イテレーター
            public IEnumerable<T> TopN( int n )
            {
                int j = this.top <= n ? 0 : this.top - n;

                for( int i = this.top; j <= --i; )
                    yield return this.values[ i ];
            }
        }
    }
}

実行結果
ジェネリックイテレーター: 9 8 7 6 5 4 3 2 1 0
top to bottom: 9 8 7 6 5 4 3 2 1 0
bottom to top: 0 1 2 3 4 5 6 7 8 9
top 5: 9 8 7 6 5




共変性・反変性
サマリー
.NET Framework 2.0, C# 2.0, Visual Studio 2005

共変性(covariance)

反変性(contravariance)

ジェネリック変性
.NET Framework 4.0, C# 4.0, Visual Studio 2010




匿名関数
サマリー
.NET Framework 2.0, C# 2.0, Visual Studio 2005

匿名関数とは、「"インライン"ステートメント、または式」のことである。

これにより、名前付きデリゲート型の代わりに、メソッドパラメーターとして渡すことが可能になる。

2 種類の匿名関数

匿名メソッド
2.0 より前の C# でデリゲートを宣言する場合、わざわざ名前付きメソッドを定義する必要があったが、2.0 でこの匿名メソッドが追加され、その場で "名前のない(匿名)" メソッドを作って、デリゲートのインスタンス化を行えるようになった。これにより、コーディングのオーバーヘッドを削減できる。

たとえば、開始されるときに呼び出されるメソッドのデリゲートをパラメーターとして取る、Thread クラスをインスタンス化する際に、ブロックで記述する方が、(メソッドを定義する)コーディングの量が減ったり、変な名前付で悩んだりしないで済む。

using System;

namespace CsFeatures.AnonymousMethod
{
    public class AnonymousMethodTest
    {
        public static void Main()
        {
            System.Threading.Thread thread = new System.Threading.Thread(
                delegate()
                {
                    // 別スレッドの処理...
                }
            );
            thread.Start();
        }
    }
}

この匿名メソッドは、上記のような使用方法だけでなく、定義されたデリゲートに対しても代入が行える。

また、匿名メソッドブロックの外部で宣言された変数(パラメーター含む)は 外部変数 と呼ばれ、ブロック内で参照できる。この変数への参照は、デリゲートが作成されたときに取り込まれ、有効期限は、匿名メソッドを参照するデリゲートが、ガベージコレクションの対象になるまで継続する。

public class AnonymousMethodTest
{
    // デリゲートを定義
    delegate void Del( int x );

    // クラス変数を定義
    private static int n = 1;

    public static void Main()
    {
        // 匿名メソッドの外部変数を定義
        int i = 0;
        Del d = delegate( int x )
        {
            Console.WriteLine( ++i );
            Console.WriteLine( ++n );
            Console.WriteLine( ++x );
            Console.WriteLine( "Hello World!" );
        };
        d( 2 );
    }
}

実行結果
1
2
3

ラムダ式
[ ラムダ式 ] を参照。



型推論
サマリー
.NET Framework 3.5, C# 3.0, Visual Studio 2008

C# で言う型推論とは、「変数に型を宣言しなくても、可能な限りで型を自動判別する機能」である。

型推論による機能

var キーワード
var キーワードを用いることにより、暗黙的に型付けされた "ローカル変数" を定義できる。このローカル変数は必ず初期値が必要であり、その初期値から型を自動判別する。

var i = 1;      // System.Int32 型
var d = 1.0;    // System.Double 型
var s = "s";    // System.String 型
var n;          // コンパイルエラー:未初期化
var n = null;   // コンパイルエラー:null 割り当て不可

配列の暗黙型付け
new にて配列を初期化する場合に、型指定を省略できる。

下記のコードは同じ意味を持つ。

int[] oldarray = new int[] { 0, 1, 2 };
int[] newarray = new[] { 0, 1, 2 };

匿名型
下記コードの様に定義すると、実態は自動生成され、その後 people.FirstName とアクセスできるが、生成されたクラスは参照できない。

また、このクラスはその場限りのものであるが、LINQ の select クエリ式で使用することが期待される。

var people = { LastName = "谷口", FirstName = "タカオ" };




ラムダ式
サマリー
.NET Framework 3.5, C# 3.0, Visual Studio 2008

ラムダ式とは、「式(z = x + y;)と、ステートメント(文)を含むことができる 匿名関数 」であり、デリゲート型、または式ツリー型を作成するために使用される。

ラムダ式は C# 3.0 で導入された機能であり、C# 2.0 の匿名メソッドと概念は同じであるが、より簡潔に記述できるようになった。

using System;
using System.Linq;
using System.Linq.Expressions;

namespace CsFeatures.Lambda
{
    public class LambdaTest
    {
        delegate int del( int i );

        public static void Main()
        {
            // デリゲート型への割り当て
            del myDelegate = x => x * x;
            int j = myDelegate( 5 );
            Console.WriteLine( j );

            // 式ツリー型の作成
            Expression<del> myET = x => x * x;
            Console.WriteLine( myET.Compile()( 10 ) );
        }
    }
}

実行結果
25
100

式形式
右辺(本体)に式があるラムダ式を、「式形式のラムダ」と呼ぶ。式の結果を返す。

基本形式は以下のとおり。

(input parameters) => expression

省略可能なもの
  • 入力パラメーターの型
    ⇒ ラムダ本体、デリゲート型からコンパイラが推論
  • カッコ ()
    ⇒ 入力パラメーターが 1 つの場合のみ
  • return ステートメント
    ⇒ 簡略化(式形式の場合は書けない)

// x == y を返却
(x, y) => x == y

// パラメーター以外省略
x => Console.Write( x )

// 明示してもよい(return 以外)
(int x, string s) => s.Length > x

// パラメーターがない場合は、下記のように () を記述
() => Console.Write( "hello" )

ステートメント形式
右辺(本体)がステートメント(文)からなるラムダ式を、「ステートメント形式のラムダ」と呼ぶ。ステートメント形式のラムダを使用して、式ツリーを作成することはできない。

基本形式は以下のとおり。

(input parameters) => { statement; }

ステートメント形式では、任意のステートメントで構成できるが、実際面では 2、3 個以下にするのがよい。また、中括弧 {} および return ステートメント(値を返す場合のみ)が必須になる。

using System;
using System.Linq;
using System.Linq.Expressions;

namespace CsFeatures.Lambda
{
    public class LambdaTest
    {
        delegate string TestDelegate( string s );

        public static void Main()
        {
            TestDelegate myDel = ( string n ) =>
            {
                string s = n + " " + "C#";
                Console.Write( s );
                return " World";
            };
            Console.WriteLine( myDel( "Hello" ) );
        }
    }
}

実行結果
Hello C# World

汎用デリゲートの使用
ラムダ式を使用する際にデリゲートをいちいち定義するのは、多少ではあるがコーディングのオーバーヘッドが大きくなる。

これを解決するため、Action、Func などの "汎用デリゲート" が用意されている。

例えば、上記のステートメント形式のラムダを汎用デリゲートで記述すると、以下のようになる(結果は同じ)。

using System;
using System.Linq;
using System.Linq.Expressions;

namespace CsFeatures.Lambda
{
    public class LambdaTest
    {
        public static void Main()
        {
            Func<string, string> myFunc = ( string n ) =>
            {
                string s = n + " " + "C#";
                Console.Write( s );
                return " World";
            };
            Console.WriteLine( myFunc( "Hello" ) );
        }
    }
}

汎用デリゲート一覧
[ 汎用デリゲート一覧 ] を参照。

標準クエリ演算子におけるラムダ式
標準クエリ演算子の多くは、汎用デリゲートの Func<T, TResult> に属する型の入力パラメーターを持つ。

例えば、標準クエリ演算子である、Count メソッド(オーバーロードメソッドの内、条件を満たす要素の数を取得するメソッドの場合)は、Func<IEnumerable<TSource>, Func<TSource, Boolean>> として定義され、以下のように使用できる。

using System;
using System.Linq;
using System.Linq.Expressions;

namespace CsFeatures.Lambda
{
    public class LambdaTest
    {
        public static void Main()
        {
            int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
            int oddNumbers = numbers.Count( n => n % 2 == 1 );
            Console.WriteLine( oddNumbers );
        }
    }
}

実行結果
5




式ツリー
サマリー
.NET Framework 3.5, C# 3.0, Visual Studio 2008

式ツリーとは、「コードをツリー形式で表す機能」である。これにより、実行可能なコードの動的な変更、多様なデータベースの LINQ クエリの実行、動的クエリの作成を可能とする。




LINQ
サマリー
.NET Framework 3.5, C# 3.0, Visual Studio 2008

LINQ (Language INtegrated Query : 統合言語クエリ) とは、「クエリ機能を .NET 言語に直接統合する一連の技術」である。

LINQ を用いることにより、様々なタイプのデータソースに対する操作(検索など)を、共通した(SQL ライクな select from where などの)構文で行うことができるようになる。

また、従来のデータに対するクエリは、単純な文字列であり、そのために受けれないコンパイラの恩恵が多々あった。例えば構文や型のチェック、そしてIntelliSenseである。これらは LINQ を使用することにより、全て行ってくれるようになる。

LINQ の構文を以下に示す。基本的にはどの LINQ でも構文は同じであり、そこが LINQ の素晴らしい点である。

var query =
    from i in collection
    where i < 3
    select i;

LINQ の種類

LINQ to SQL
※ この例では DataContext 関連を省略している。詳細は C# de DataContext を参照。

LINQ to SQL は、その名が示すとおり、リレーショナルデータベース(RDB)を対象とした LINQ である。

例として、以下のテーブルがデータベースに存在する。

Players テーブル
IdNameGrade
1先代キャプテン3
2谷口キャプテン2
3松下2
4丸井1

Positions テーブル
IdNamePlayerId
1不明1
2サード2
3ピッチャー2
4ピッチャー3
5セカンド4


データ取得

まずデータを取得するコード。Positions.PlayerId は Players.Id の外部キーになる。

using System;
using System.Linq;

namespace CsFeatures.Linq.ToSql
{
    public class Linq2SqlTest
    {
        public static void Main()
        {
            var connectionString = "
                Data Source=datasource;
                Initial Catalog=dbname;
                User ID=login;Password=password";

            var database = new Linq2SqlTestDataContext( connectionString );

            // 1年生の選手
            var y1Players =
                from p in database.Players
                where p.Grade == 1
                select p;

            Console .WriteLine( "1年生:"  );
            foreach( var y1Player in y1Players )
                Console.WriteLine( "{0}", y1Player.Name );
            Console.WriteLine();

            // ピッチャー
            var pitchers =
                from p in database.Positions
                where p.Name.Contains( "ピッチャー" )
                orderby p.Players.Grade descending
                select new 
                { 
                    Name = p.Players.Name
                    , Grade = p.Players.Grade
                };

            Console .WriteLine( "ピッチャー:"  );
            foreach ( var  pitcher in  pitchers )
                Console .WriteLine( "\t{0}:{1} 年生"
                     , pitcher.Name, pitcher.Grade );
        }
    }
}

実行結果
1年生:
    丸井

ピッチャー:
    谷口タカオ:2 年生
    松下:2 年生


データ削除

下記コードを実行すると、Players テーブルから先代キャプテン、Positions テーブルから先代キャプテンのポジションデータが削除される。

削除の実際としては、DeleteOnSubmit() で削除データをコレクションから削除し、データベースへの転送は SubmitChanges() により行われる。

また、LINQ to SQL は連鎖削除操作(親データを削除すると、外部キーが定義された子データも削除する機能)をサポートしていないため、
  • まず子データを削除し、その後、親データを削除する(コーディングで実現)
  • 外部キー制約で ON DELETE CASCADE を設定(Database の機能)
のどちらかで対応するが、コーディングで実装すると削除忘れやコーディングのオーバーヘッドに絡むため、ON DELETE CASCADE を推奨する。

// 削除対象となるプレイヤーを取得。ここでは 3 年生が引退する。
var deletePlayers =
    from p in database.Players
    where p.Grade == 3
    select p;

// 削除対象データをコレクションから削除
foreach( var deletePlayer in deletePlayers )
{
    // プレイヤー
    database.Players.DeleteOnSubmit( deletePlayer );

    // プレイヤーのポジション
    var deletePositions =
        from p in database.Positions
        where p.PlayerId == deletePlayer.Id
        select p;
    foreach( var deletePosition in deletePositions )
        database.Positions.DeleteOnSubmit( deletePosition );
}

// 実削除
if( 0 < deletePlayers.Count() )
    database.SubmitChanges();

削除後のPlayers テーブル
IdNameGrade
2谷口キャプテン2
3松下2
4丸井1

削除後のPositions テーブル
IdNamePlayerId
2サード2
3ピッチャー2
4ピッチャー3
5セカンド4


データ更新

データの更新。まずはデータを取得し、それに対して変更を行う。

// 対象データを取得
var players =
    from p in database.Players
    select p;

// データを更新
// ここでは学年をインクリメントする。
foreach( var player in players )
    player.Grade++;

// 更新
database.SubmitChanges();

更新後のPlayers テーブル
IdNameGrade
2谷口キャプテン3
3松下3
4丸井2


データ挿入

データの挿入は下記のように行う。削除と同様、InsertOnSubmit() の後、SubmitChanges() を行う。

/* 
 * 新1年生
 */
Players player = new Players();
player.Name = "イガラシ";
player.Grade = 1;

// 登録データ
database.Players.InsertOnSubmit( player );

// 実登録(ポジション登録時の PlayerId 取得のためここで登録)
database.SubmitChanges();

/* 
 * イガラシのポジション
 * First() は、先頭レコードを取得
 */
var igarashi =
    (from p in database.Players
    where p.Name.Contains( "イガラシ" )
    select p).First();

// 登録データ
Positions third = new Positions();
third.Name = "サード";
third.PlayerId = igarashi.Id;
database.Positions.InsertOnSubmit( third );

Positions second = new Positions();
second.Name = "セカンド";
second.PlayerId = igarashi.Id;
database.Positions.InsertOnSubmit( second );

Positions pitcher = new Positions();
pitcher.Name = "ピッチャー";
pitcher.PlayerId = igarashi.Id;
database.Positions.InsertOnSubmit( pitcher );

// 実登録
database.SubmitChanges();

更新後のPlayers テーブル
IdNameGrade
2谷口キャプテン3
3松下3
4丸井2
5イガラシ1

更新後のPositions テーブル
IdNamePlayerId
2サード2
3ピッチャー2
4ピッチャー3
5セカンド4
6サード5
7セカンド5
8ピッチャー5


まとめ

LINQ to SQL を用いることにより、SQL 地獄から解放されるだけでなく、データの操作とビジネスロジックが一貫した形になるため、可読性も格段に向上する。

LINQ to XML
LINQ to XML とは、「.NET Framework 言語から XML を操作できるようにする、LINQ に対応したメモリ内 XML プログラミングインターフェイス」である。

概念としては DOM(ドキュメント オブジェクト モデル)に類似しているが、LINQ を利用する点が異なる。これは、XML ドキュメントに対してクエリを記述して操作が行えることを意味する。つまり XML ドキュメント内のアイテムを取得するときに、条件や順序を指定できる。

XML ファイルの生成

XML ファイルを生成するには、XDocument クラスを使用する。XDocument クラスや XElement、また XAttribute のコンストラクターは複数のコンテンツを引数として取るため、下記コードのようにXML ツリーを生成できる。ファイルへの保存は save() するだけ。

using System;
using System.Xml.Linq;

namespace CsFeatures.Linq.ToXml
{
    public class Linq2XmlTest
    {
        static string path = @"c:players.xml";
        public static void Main()
        {
            XDocument xml = new XDocument(
                new XDeclaration( "1.0", "utf-8", "yes" )
                new XElement( "Players"
                    , new XElement( "Player", new XAttribute( "Post", "Captain" )
                        , new XElement( "Name", "谷口 タカオ" )
                        , new XElement( "Grade", 3 )
                        , new XElement( "PrimaryPosition", "サード" )
                        , new XElement( "SecondaryPosition", "ピッチャー" )
                        , new XElement( "TertiaryPosition" )
                    )
                    , new XElement( "Player", new XAttribute( "Post", "Vice captain" )
                        , new XElement( "Name", "小山" )
                        , new XElement( "Grade", 3 )
                        , new XElement( "PrimaryPosition", "キャッチャー" )
                        , new XElement( "SecondaryPosition" )
                        , new XElement( "TertiaryPosition" )
                    )
                    , new XElement( "Player"
                        , new XElement( "Name", "松下" )
                        , new XElement( "Grade", 3 )
                        , new XElement( "PrimaryPosition", "ピッチャー" )
                        , new XElement( "SecondaryPosition" )
                        , new XElement( "TertiaryPosition" )
                    )
                    , new XElement( "Player"
                        , new XElement( "Name", "丸井" )
                        , new XElement( "Grade", 2 )
                        , new XElement( "PrimaryPosition", "セカンド" )
                        , new XElement( "SecondaryPosition" )
                        , new XElement( "TertiaryPosition" )
                    )
                    , new XElement( "Player"
                        , new XElement( "Name", "イガラシ" )
                        , new XElement( "Grade", 1 )
                        , new XElement( "PrimaryPosition", "セカンド" )
                        , new XElement( "SecondaryPosition", "ピッチャー" )
                        , new XElement( "TertiaryPosition", "サード" )
                    )
                )
            );

            xml.Save( path );
        }
    }
}

出力された XML ファイル
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Players>
  <Player Post="Captain">
    <Name>谷口 タカオ</Name>
    <Grade>3</Grade>
    <PrimaryPosition>サード</PrimaryPosition>
    <SecondaryPosition>ピッチャー</SecondaryPosition>
    <TertiaryPosition />
  </Player>
  <Player Post="Vice captain">
    <Name>小山</Name>
    <Grade>3</Grade>
    <PrimaryPosition>キャッチャー</PrimaryPosition>
    <SecondaryPosition />
    <TertiaryPosition />
  </Player>
  <Player>
    <Name>松下</Name>
    <Grade>3</Grade>
    <PrimaryPosition>ピッチャー</PrimaryPosition>
    <SecondaryPosition />
    <TertiaryPosition />
  </Player>
  <Player>
    <Name>丸井</Name>
    <Grade>2</Grade>
    <PrimaryPosition>セカンド</PrimaryPosition>
    <SecondaryPosition />
    <TertiaryPosition />
  </Player>
  <Player>
    <Name>イガラシ</Name>
    <Grade>1</Grade>
    <PrimaryPosition>セカンド</PrimaryPosition>
    <SecondaryPosition>ピッチャー</SecondaryPosition>
    <TertiaryPosition>サード</TertiaryPosition>
  </Player>
</Players>



XML ファイルの読込

単純に読み込むだけであれば、下記コードでよい。

XDocument doc = XDocument.Load( path );

SQL ライクな条件、順序を付与して記述すると下記のようになる。LINQ to SQL と同じ。下記は XElement クラスで実装しているが、XDocument でもよい。その場合、Elements ではなく Descendants メソッドを使用する。

const string Pitcher = "ピッチャー";

var xe = XElement.Load( path );

// ピッチャーを取得
var pitchers =
    from e in xe.Elements( "Player" )
    where ( e.Element( "PrimaryPosition" ).Value.Contains( Pitcher )
        || e.Element( "SecondaryPosition" ).Value.Contains( Pitcher )
        || e.Element( "TertiaryPosition" ).Value.Contains( Pitcher ) )
    orderby ( int )e.Element( "Grade" )
    select new { Name = e.Element( "Name" ).Value
        , Grade = e.Element( "Grade" ).Value };
Console.WriteLine( "{0}:", Pitcher );
foreach( var pitcher in pitchers )
    Console.WriteLine( "\t{0}:{1} 年生", pitcher.Name, pitcher.Grade );
Console.WriteLine();

// 2 年生を取得
var secondYears =
    from e in xe.Descendants( "Player" )
    where int.Parse( e.Element( "Grade" ).Value ) == 2
    select new { Name = e.Element( "Name" ).Value
        , Position = e.Element( "PrimaryPosition" ).Value };
Console.WriteLine( "2 年生:" );
foreach( var sec in secondYears )
    Console.WriteLine( "\t{0}:{1}", sec.Name, sec.Position );

実行結果
ピッチャー:
    イガラシ:1 年生
    谷口 タカオ:3 年生
    松下:3 年生

2 年生:
    丸井:セカンド


要素の追加

単純に追加する方法(XElement 生成)と、コピーして追加する方法(XElement を取得)がある。下記ではコピー元を Last() にて取得しているが、First() でも、別の条件で 1 件に絞ってもよい。

var xe = XElement.Load( path );

// 単純追加
xe.Add( 
    new XElement( "Player"
        , new XElement( "Name", "島田" )
        , new XElement( "Grade", 2 )
        , new XElement( "PrimaryPosition", "ライト" )
        , new XElement( "SecondaryPosition" )
        , new XElement( "TertiaryPosition" )
    )
);
xe.Save( path );

// コピー追加
var xelem = (
    from elem in xe.Elements( "Player" )
    select elem ).Last();
var addElem = new XElement( xelem );
addElem.Element( "Name" ).Value = "浅間";
addElem.Element( "Grade" ).Value = 3.ToString();
addElem.Element( "PrimaryPosition" ).Value = "センター";
xe.Add( addElem );
xe.Save( path );

追加された要素
  <Player>
    <Name>島田</Name>
    <Grade>2</Grade>
    <PrimaryPosition>ライト</PrimaryPosition>
    <SecondaryPosition />
    <TertiaryPosition />
  </Player>
  <Player>
    <Name>浅間</Name>
    <Grade>3</Grade>
    <PrimaryPosition>センター</PrimaryPosition>
    <SecondaryPosition />
    <TertiaryPosition />
  </Player>



要素の削除

要素の削除は Remove() メソッドにて行う。その後、削除を保存する。

var xe = XElement.Load( path );
var xelem = (
    from e in xe.Elements( "Player" )
    where e.Element( "Name" ).Value.Contains( "浅間" )
    select e ).First();
xelem.Remove();
xe.Save( path );


要素の更新

要素の更新は 取得したエレメントに対して値を設定し、Save() を行う。

var xe = XElement.Load( path );
var elem = (
    from e in xe.Elements( "Player" )
    where e.Element( "Name" ).Value.Contains( "丸井" )
    select e ).First();
elem.Element( "Name" ).Value += " 次期Cpt.";
xe.Save( path );


まとめ

LINQ to XML を用いることにより、SQL ライクな構文で、かつ 強力な IDE の様々な恩恵を受けながら XML の操作を行える。

LINQ to Objects




拡張メソッド
サマリー
.NET Framework 3.5, C# 3.0, Visual Studio 2008

拡張メソッドとは、「クラスやインターフェイスにメソッドを拡張(追加)する機能」である。

元となるクラスやインターフェイスを書き換えたり、継承などをせず、インスタンスメソッド(のように使うことができるメソッド) を拡張できる。

拡張メソッドとして定義したメソッドは、静的メソッドとしても使用できる。

拡張メソッドの条件
  • 静的クラス.静的メソッドで記述されている
  • 第一引数の頭に this キーワードが記述されている
  • 第一引数の型が メソッド を追加する型(クラス・インターフェイス)

実装の例
メソッド、フィールドなど何も持たない OriginalClass クラスを定義し、Extensions クラスで Wrrrrrrryyyyyyyyy メソッド を拡張している。

using System;

namespace CsFeatures.ExtensionMethod
{
    // 空の オリジナルクラスを定義
    public class OriginalClass { }

    // 拡張メソッド用クラスを定義
    public static class Extensions
    {
        // OriginalClass への拡張メソッド
        public static void Wrrrrrrryyyyyyyyy( this OriginalClass oc
            , string who )
        {
            Console.WriteLine( "{0}:WRRRRRRYYYYYYYY!!!!", who );
        }
    }

    // 拡張メソッドテスト用クラス
    public static class ExtensionMethodTest
    {
        public static void Do()
        {
            var oc = new OriginalClass();
            oc.Wrrrrrrryyyyyyyyy( "Dio" );
        }
    }
}

namespace CsFeatures
{
    class Program
    {
        static void Main()
        {
            // 拡張メソッドテスト
            ExtensionMethod.ExtensionMethodTest.Do();
        }
    }
}

実行結果
Dio:WRRRRRRYYYYYYYY!!!!





演算子
標準クエリ演算子
サマリー
.NET Framework 3.5, C# 3.0, Visual Studio 2008

ちょっと毛色が違うけど... Where Select GroupBy OrderBy などなど。




??演算子(null 合体演算子)
サマリー
.NET Framework 2.0, C# 2.0, Visual Studio 2005

左側のオペランドが null 値でない場合はこのオペランドを、null 値である場合は右側のオペランドを返す。

// x が null の場合は y, null じゃない場合は x の値を z に代入
z = x ?? y;




=>演算子(ラムダ演算子)
サマリー
.NET Framework 3.5, C# 3.0, Visual Studio 2008

左辺(入力変数)と右辺(本体)の切り分けのため、ラムダ式で使用される。

// capt.1
//   文字列配列を定義し、その中で最小の桁数を取得している。
//   words.Min() は Func<string, decimal> の匿名関数として定義され、
//   string が words 中の文字列、decimal が戻り値の型(Min なので最小の桁数を返す)
//   () と 型 は省略可能( w => w.Length と記述可能 )
string[] words = { "cherry", "apple", "blueberry" };
Console.WriteLine( words.Min( ( string w ) => w.Length ) );

// capt.2
//   文字列配列を定義し、その中で桁数が添字より小さいものを取得している。
//   digits.Where() は Func<string, int, bool> の匿名関数として定義され、
//   string が digits 中の文字列、int が 添字、
//   bool が戻り値の型(Where なので => 以降の結果値を返す)
string[] digits = { "zero", "one", "two", "three", "four", "five"
    , "six", "seven", "eight", "nine" };
var shortDigits 
    = digits.Where( ( string digit, int index ) => digit.Length < index );
foreach( var sD in shortDigits )
{
    Console.WriteLine( sD );
}

実行結果
5
five
six
seven
eight
nine





雑多メモ
参照カウントの解放
サマリー
COM オブジェクトを使用する場合は、参照カウントを解放する処理が必要になる。参照カウントは ガベージコレクター の動作の一つで、これが 0 になって初めて破棄が許される。

C# で使用する場合、fainally ブロックで解放するのが間違いのない動作。Excel のように Application, Workbook, Worksheets...毎で finally ブロックを記述するのは冗長になるので気をつける。

Excel.Workbook book;

try
{
    // ... 略
}
finally
{
    if( book != null ) 
    {
        book.Close( true, Type.Missing, Type.Missing );
        System.Runtime.InteropServices.Marshal.ReleaseComObject( book );
    }
}




String.Format 書式
サマリー
String.Format で指定する書式を備忘録として。忘れっぽい人向けに。

わたしです^o^

※ (default) は未指定で、その場合 (G) が適用される。

using System;

namespace CsFeatures.Strings
{
    public static class FormatTest
    {
        enum Color { Yellow = 1, Blue, Green };

        public static void Do()
        {
            string s = "";

            Console.Clear();

            // 数値フォーマット
            Console.WriteLine( "数値書式----------" );
            s = String.Format(
                "(C) Currency: . . . . . . . {0:C}\n" +
                "(D) Decimal:. . . . . . . . {0:D}\n" +
                "(E) Scientific: . . . . . . {1:E}\n" +
                "(F) Fixed point:. . . . . . {1:F}\n" +
                "(G) General:. . . . . . . . {0:G}\n" +
                "    (default):. . . . . . . {0} (default = 'G')\n" +
                "(N) Number: . . . . . . . . {0:N}\n" +
                "(P) Percent:. . . . . . . . {1:P}\n" +
                "(R) Round-trip: . . . . . . {1:R}\n" +
                "(X) Hexadecimal:. . . . . . {0:X}\n",
                -123, -123.45f );
            Console.WriteLine( s );

            // 日付フォーマット
            Console.WriteLine( "日付書式----------" );
            s = String.Format(
                "(d) Short date: . . . . . . {0:d}\n" +
                "(D) Long date:. . . . . . . {0:D}\n" +
                "(t) Short time: . . . . . . {0:t}\n" +
                "(T) Long time:. . . . . . . {0:T}\n" +
                "(f) Full date/short time: . {0:f}\n" +
                "(F) Full date/long time:. . {0:F}\n" +
                "(g) General date/short time:{0:g}\n" +
                "(G) General date/long time: {0:G}\n" +
                "    (default):. . . . . . . {0} (default = 'G')\n" +
                "(M) Month:. . . . . . . . . {0:M}\n" +
                "(R) RFC1123:. . . . . . . . {0:R}\n" +
                "(s) Sortable: . . . . . . . {0:s}\n" +
                "(u) Universal sortable: . . {0:u} (invariant)\n" +
                "(U) Universal sortable: . . {0:U}\n" +
                "(Y) Year: . . . . . . . . . {0:Y}\n",
                DateTime.Now );
            Console.WriteLine( s );

            // 列挙値フォーマット
            Console.WriteLine( "列挙値書式----------" );
            s = String.Format(
                "(G) General:. . . . . . . . {0:G}\n" +
                "    (default):. . . . . . . {0} (default = 'G')\n" +
                "(F) Flags:. . . . . . . . . {0:F} (flags or integer)\n" +
                "(D) Decimal number: . . . . {0:D}\n" +
                "(X) Hexadecimal:. . . . . . {0:X}\n", 
                Color.Green );
            Console.WriteLine( s );
        }
    }
}

namespace CsFeatures
{
    class Program
    {
        static void Main()
        {
            // String.Format 書式テスト
            Strings.FormatTest.Do();
        }
    }
}

実行結果
数値書式----------
(C) Currency: . . . . . . . . -\123
(D) Decimal:. . . . . . . . . -123
(E) Scientific: . . . . . . . -1.234500E+002
(F) Fixed point:. . . . . . . -123.45
(G) General:. . . . . . . . . -123
(default):. . . . . . . . . . -123 (default = 'G')
(N) Number: . . . . . . . . . -123.00
(P) Percent:. . . . . . . . . -12,345.00%
(R) Round-trip: . . . . . . . -123.45
(X) Hexadecimal:. . . . . . . FFFFFF85

日付書式----------
(d) Short date: . . . . . . . 2011/03/16
(D) Long date:. . . . . . . . 2011年3月16日
(t) Short time: . . . . . . . 15:24
(T) Long time:. . . . . . . . 15:24:25
(f) Full date/short time: . . 2011年3月16日 15:24
(F) Full date/long time:. . . 2011年3月16日 15:24:25
(g) General date/short time:. 2011/03/16 15:24
(G) General date/long time: . 2011/03/16 15:24:25
(default):. . . . . . . . . . 2011/03/16 15:24:25 (default = 'G')
(M) Month:. . . . . . . . . . 3月16日
(R) RFC1123:. . . . . . . . . Wed, 16 Mar 2011 15:24:25 GMT
(s) Sortable: . . . . . . . . 2011-03-16T15:24:25
(u) Universal sortable: . . . 2011-03-16 15:24:25Z (invariant)
(U) Universal sortable: . . . 2011年3月16日 6:24:25
(Y) Year: . . . . . . . . . . 2011年3月

列挙値書式----------
(G) General:. . . . . . . . . Green
(default):. . . . . . . . . . Green (default = 'G')
(F) Flags:. . . . . . . . . . Green (flags or integer)
(D) Decimal number: . . . . . 3
(X) Hexadecimal:. . . . . . . 00000003