.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 と呼ぶ。 下記の例では、デリゲートを代入する際の 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 ジェネリックとは、「多種多様な型に対応するために、パラメーターとして型を与え(型パラメーター)、クラスやメソッドを設計(定義)する機能」である。 インスタンス化されるまで、型の指定を遅延させるクラスやメソッドを設計(定義)できる。コレクションなどで、格納されているデータの型を "コレクション側で固定にしない"、"使用する側で型を指定" する。 ジェネリックを使用することによる利点
ジェネリッククラスの例を以下に示す。至極単純なリンクリスト クラスを作っている。 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 ステートメントで呼び出される "反復処理"」である。イテレーターブロックを用いてこれを実現する。 イテレーターのポイント
以下は、非ジェネリックのコード。単純に曜日名を列挙するクラス。 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 .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 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 省略可能なもの
// 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 クエリの実行、動的クエリの作成を可能とする。 .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 の種類 ※ この例では DataContext 関連を省略している。詳細は C# de DataContext を参照。 LINQ to SQL は、その名が示すとおり、リレーショナルデータベース(RDB)を対象とした LINQ である。 例として、以下のテーブルがデータベースに存在する。
データ取得 まずデータを取得するコード。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 は連鎖削除操作(親データを削除すると、外部キーが定義された子データも削除する機能)をサポートしていないため、
// 削除対象となるプレイヤーを取得。ここでは 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();
データ更新 データの更新。まずはデータを取得し、それに対して変更を行う。 // 対象データを取得
var players =
from p in database.Players
select p;
// データを更新
// ここでは学年をインクリメントする。
foreach( var player in players )
player.Grade++;
// 更新
database.SubmitChanges();
データ挿入 データの挿入は下記のように行う。削除と同様、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();
まとめ LINQ to SQL を用いることにより、SQL 地獄から解放されるだけでなく、データの操作とビジネスロジックが一貫した形になるため、可読性も格段に向上する。 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 の操作を行える。 .NET Framework 3.5, C# 3.0, Visual Studio 2008 拡張メソッドとは、「クラスやインターフェイスにメソッドを拡張(追加)する機能」である。 元となるクラスやインターフェイスを書き換えたり、継承などをせず、インスタンスメソッド(のように使うことができるメソッド) を拡張できる。 拡張メソッドとして定義したメソッドは、静的メソッドとしても使用できる。 拡張メソッドの条件
メソッド、フィールドなど何も持たない 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 などなど。 .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 で指定する書式を備忘録として。忘れっぽい人向けに。 わたしです^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 |
C# Notes
C# 関連のメモ。サマリーなど MSDN ライブラリ または MSDN:C# プログラミング ガイド より引用。
登録:
コメント (Atom)
