宇宙仮面の
C# Programming

 
  • twitter
  • Blog
  • Twitter
  • Facebook
  • uchukamen
  • C# on Windows 8
  • チュートリアル
  • 開発環境
  • WPF and ASP.NET
  • Windows Forms
  • SQL Server

.NET Framework の歴史

いつ、どのような機能が追加されたのか、わけがわからなくなってきたので、整理。

.NET Framework Version: 1.0

開発環境 Visual Studio 2002

  • Code Name: Rainier
  • 内部バージョン: 7.0

C# Version 1.0

  • 初期バージョン

.NET Framework Version: 1.1

開発環境 Visual Studio 2003

  • Code Name: Everett
  • 内部バージョン: 7.1
  • 機能拡張
    • .NET Compact Framework 1.0搭載
    • IntelliSense の機能向上
    • デバッガの機能向上(コレクションクラスのデータ表示)
    • コンパイル方法や構築方法のカスタマイズ
    • J# 対応
    • ASP .NET Mobille Controls

C# Version 1.2

  • ECMA C# 言語仕様の完全サポート

.NET Framework Version: 2.0

開発環境 Visual Studio 2005

  • Code Name: Whidbey
  • 内部バージョン: 8.0

C# 2.0

  • Generics
  • iterator
  • partial class
  • Nullable 型
  • 匿名メソッド
  • 名前空間のエイリアス修飾子
  • 静的クラスの変更
  • 外部アセンブリのエイリアス
  • プロパティ アクセサのアクセシビリティ
  • デリゲートの共変性と反変性
  • デリゲートの宣言の簡素化
  • 固定サイズ バッファ
  • フレンド アセンブリ
  • #pragma 警告
  • volatile
  • C# コンパイラ オプションの変更

.NET Framework Version: 3.0

背景

  • Ajaxなどのユーザーエクスペリエンスの向上
  • 相互接続性、セキュリティの向上
  • Vistaに標準で .NET Framework 3.0が搭載された。
  • Windows XP, Windows Server 2003 (Service Pack 1 以上)もサポート
  • 開発環境は Visual Studio 2005 のままで、.NET Framework 3.0を搭載することにより、Vista上でのWPFなどの新機能を実装できるようになった。
  • Vista US版 RTM 2006/11/8。
  • Vista 日本語版 発売開始は、2007/1/30

リリース日

  • 2006/11/7 RTM

.NET Framework 3.0の構成

  • .NET Framework 2.0
  • WPF(Windows Presentation Foundation)
  • WCF(Windows Communication Foundation)
  • WF(Windows Workflow Foundation)
  • WCS(Windows CardSpace)

開発環境 Visual Studio 2005

  • Code Name: Whidbey
  • 内部バージョン: 8.0

.NET Framework Version: 3.5

開発環境 Visual Studio 2008

  • Code Name: Orcas
  • 内部バージョン: 9.0
  • 機能拡張
  1. .NET Framework の複数バージョンをサポート
  2. LINQ
  3. Office アプリケーション対応(VSTO, Office 2007のリボンなど)
  4. Professionalに単体テスト機能
  5. Expression Web, Expression Blend 対応
  6. Windows Vista 用の魅力的なアプリケーションの構築 (WPF)
  7. WEB対応強化(AJAX, JSON対応。WCFによるRSS, REST対応)
  8. WCF
  9. CSSサポートの強化
  10. 全体的なパフォーマンスと安定性
  11. BigInteger Class
  12. HashSet Class
  13. System.IO.Pipes 名前空間 (匿名パイプと名前付きパイプ)

C# 3.0

  • LINQ
    • var   型推論
    • 匿名型
    • ラムダ式

C# 2.0の主な追加機能

Generics

.Net Framework 2.0から、System.Collections.Generic という新しい名前空間が追加されました。

.NET Framework 1.0 の System.Collections.ArrayList では、Object にいろいろな値を格納することができた。しかし、次のような問題がありました。

  • データの格納、取り出しには、これはボックス化、ボックス化解除が必要であり、効率的ではない。

  • Objectに格納されるため、コンパイル時にエラーになる場合がある。

これに対してGenericsは、コンパイル時にタイプセーフなコレクションを作成できます。ArrayList と List <T> は似ていますが、Tが値型の場合 List <T>のほうが高速です。

  System.Collections System.Collections.Generics
リスト関連 ArrayList List<T>
  LinkedList(T)
SortedList SortedList(TKey, TValue)
辞書 DictionaryBase Dictionary(TKey, TValue)
  SortedDictionary(TKey, TValue)
ハッシュ Hashtable HashSet(T)
キュー Queue Queue(T)
スタック Stack Stack(T)

例 

private static void ListForeach()
{
 
List<int> list = new List<int>();
  list.Add(0);
  list.Add(1);
  list.Add(2);
 
foreach (int i in list)

 
Console.WriteLine(i.ToString());
 
Console.ReadLine();
}

匿名メソッド

C# 1.0, 1.2 までは、次のようにデリゲートの宣言と本体を別々に定義する必要がありました。

    class Program
    {
     
    static void Main(string[] args)
      {
        System.Threading.
    ThreadStart deleg = new System.Threading.ThreadStart(myFunc);
        System.Threading.
    Thread t1 = new System.Threading.Thread(deleg);

        t1.Start();

       
    Console.ReadLine();
      }

       
    static void myFunc()
        {
         
    Console.WriteLine("Hello: C# 1.0");
        }
      }
    }

C# 2.0 の匿名メソッドの導入により、次のように簡単に書けるようになります。

    class Program
    {
     
    static void Main(string[] args)
      {
        System.Threading.
    Thread t2 = new System.Threading.Thread(
           
    delegate() { Console.WriteLine("Hello: C# 2.0");});

        t2.Start();

       
    Console.ReadLine();
      }
    }

iterator

従来の .NET 1.0, 1.1 の例

従来、foreach で扱えるようなコレクションを作成するためには、IEnumerable, IEnumerator を実装する必要がありました。たとえば、System.DateTime 構造体を foreach で回すことはないと思いますが、年、月、日を foreach で取り出すコードを実装すると次のようになります。

using System;
using
System.Collections;

public
class MyItem
{
 
private Object _obj;

 
public MyItem(Object obj)
  {
   
this._obj = obj;
  }

 
public override string ToString()
  {
   
return (_obj.GetType().ToString() + ":" + _obj.ToString());
  }
}


public
class MyList : IEnumerable
{
 
public MyList(DateTime x)
  {
    n = x;
  }
  System.
DateTime n;

 
public IEnumerator GetEnumerator()
  {
   
return new IMyEnumerator(n);
  }
}

public
class IMyEnumerator : IEnumerator
{
 
public int[] myItem = new int[3];

 
int position = -1;

 
public IMyEnumerator(DateTime x)
  {
    myItem[0] = x.Year;
    myItem[1] = x.Month;
    myItem[2] = x.Day;
  }

 
public bool MoveNext()
  {
    position++;
   
return (position < myItem.Length);
  }

 
public void Reset()
  {
    position = -1;
  }

 
public object Current
  {
   
get
    {
     
try
      {
       
return myItem[position];
      }
     
catch (IndexOutOfRangeException)
      {
       
throw new InvalidOperationException();
      }
    }
  }
}


class
App
{
 
static void Main()
  {
   
MyList myList = new MyList(DateTime.Now);

   
foreach (int myItem in myList)
     
Console.WriteLine(myItem.ToString());

    Console
.ReadLine();
  }
}

C# 2.0 では

C# 2.0 では、yield return, yield break という iterator が導入され、同じことが次のように非常に簡潔に書けるようになりました。

using System;

public
class MyList : System.Collections.IEnumerable
{
  public
MyList(DateTime x)
  {
    n = x;
  }

  System.DateTime n;

  public
System.Collections.IEnumerator GetEnumerator()
  {
    yield
return n.Year;
    yield
return n.Month;
    yield
return n.Day;
  }
}

class
App
{
  static
void Main()
  {
    MyList
myList = new MyList(DateTime.Now);
    foreach
(int myItem in myList)
      Console
.WriteLine(myItem.ToString());

    Console
.ReadLine();
  }
}

partial class

Form1.cs と、Form1.Designer.cs で自動生成されるコードと開発者が実装するコードが partical class により分離されている。

Form1.cs の例

namespace WindowsFormsApplication1
{
 
public partial class Form1 : Form
  {
   
public Form1()
    {
      InitializeComponent();
    }
  }
}

Form1.Designer.cs の例

namespace WindowsFormsApplication1
{
 
partial class Form1
 
{
  ///
Windows フォーム デザイナで生成されたコード >

  }
}

Nullable 型

Nullable 型 (null 許容型)とは

最近、個人情報の扱いが厳しくなっていますが、データベースでは、NULL を許容する宣言が可能です。たとえば、個人情報のテーブルを作成し、電話番号の入力は任意とし、電話番号は NULL を許容する設定にすることができます。電話番号であれば、String クラスを使うことになると思いますが、この場合 class は参照型なので、null を扱うことができます。一方、生年月日を扱う場合、これも個人情報なので入力は任意としたいという要求があったとしましょう。.NET Framework では、DateTime 型は構造体なので値型です。したがって、null を扱うことができません。このような値型に対して、Null を許容するために導入されたのが Nullable 型です。


構造体(値型)の DateTime 変数に null を代入しようとすると、「エラー 1 Null 非許容の値型であるため、null を 'System.DateTime' に変換できません。」 というエラーになる。

DateTime dt = null;   // エラー

null 許容型を使うことにより、値型でも null を扱うことができるようになる。

DateTime? dt = null;    // null を扱える。

null 許容型の実体は、System.Nullable 構造体

int? x = null;

これは、次の省略形です。

Nullable<int> x = null;

メンバーに null が来る可能性があるため、次のメンバーが用意されています。

メンバー 説明
HasValue 現在の Nullable オブジェクトに値があるかどうか。
Value 現在の Nullable 値の値を取得します。



int
? n = null;

if (n.HasValue)
  Console.WriteLine(n.Value);
else
  Console
.WriteLine("null です。");

null 許容型を使う場合の注意

NULL を許容するということは、データが NULL であった場合の処理が必要になります。このとき、単に データを参照するだけならいいのですが、論理式の変数として NULL が入り込むと厄介なことになります。

NULL の弊害に関しては、NULL撲滅委員会 に詳しくまとまっていますので、ご一読されるとよいでしょう。個人的には、NULL を使用する変数が参照されるだけなら、Nullable 型を使用したほうがコードが見やすくなり、良いと思います。一方、NULL を使用する変数を元に、計算を行う必要がある場合には、極力 NULL を避けるべきです。特に、NULL を許容する論理計算は最悪です。

次の論理計算を実行した場合、

bool?[] bxarray = { true, false, null };
bool
?[] byarray = { true, false, null };

foreach (bool? y in byarray)
 
foreach (bool? x in bxarray)
  {
     WriteVal(x, y, x | y);  // 結果を出力
     WriteVal(x, y, x & y); // 結果を出力
  }

次のような結果になります。null が入った場合の論理和、論理積の結果で、赤でマークした部分に注目してください。このような 3値論理計算を間違いなくこなせますか?安易に Null を許容するのはやめたほうがいいでしょう。

X Y x & y x | y
true true true true
false true false true
false false false false
null true null true
null false false null

?? 演算子

?? 演算子は、左側のオペランドが null 値でない場合にはこのオペランドを返し、null 値である場合には右側のオペランドを返します。

例1

nullableInteger = null;
Console.WriteLine((nullableInteger ?? -9999).ToString());
これは -9999を返す。

?? 演算子は、参照型にも適用可能です。

string str = "Hello World";
Console.WriteLine((str ?? "null だよ").ToString());
str = null;
Console.WriteLine((str ?? "null だよ").ToString());
これは、"nullだよ" と出力します。

名前空間のエイリアス修飾子 ( ::演算子 )

基本的には、Systemで始まるような名前空間や類似の名前空間を避ければ問題ないはずですが、大規模開発でどうしても名前空間がぶつかってしまう場合には、この方法でグローバル名前空間のルートから指定できるようになります。

using sys = System;
using Uchukamen;

namespace Uchukamen
{
 
class Console
  {
   
public static void WriteLine()
    {
     
// Console.WriteLine("Hello World 1");
      // これは、自分自身の Uchukamen.Console.WriteLine() を呼び出してしまう。

      // :: 演算子により、System.Console.WriteLine を呼び出せるようになる。
      // global:: と指定した場合、グローバル名前空間のルートから検索してくれます。
   
  global::System.Console.WriteLine("Hello World 2");

   
  // sys::Console は、System.Console と同じ意味になります。
      sys::
Console.WriteLine("Hello World 3");
    }
  }

 
class program {
   
static void Main(string[] args)
    {
      Uchukamen.
Console.WriteLine();

     
global::System.Console.ReadLine();
    }
  }
}

静的クラス

静的メンバしか定義できないクラスを作ることが出来ます。

public static class StaticClass
{
 
public static void StaticMethod()
  {
   
Console.WriteLine("StaticClass: StaticMethod");
  }
}

Visual Studio 2003 で静的クラスを宣言しようとすると、「名前空間にフィールドやメソッドのようなメンバを直接含めることはできません。」というエラーになります。

外部アセンブリのエイリアス

同じ完全修飾型名を持つ 2 つのアセンブリを参照するには、コンパイル時のオプションで次のようにエイリアスを指定します。


csc /r:GridV1=grid.dll
csc /r:GridV2=grid20.dll

これにより、外部エイリアスとして、GridV1 と GridV2 が定義されます。
extern キーワードを使用してプログラム内からこれらのエイリアスを参照することができます。


extern alias GridV1;
extern alias GridV2;

プロパティ アクセサのアクセシビリティ

VS2005 以降だと、プロパティの get, set アクセサに対して、アクセシビリティ(private, protected, internal, public) を指定できるようになりました。

VS 2003 だと、「修飾子をプロパティまたはイベント アクセサ宣言に付属させることはできません。」というコンパイル時エラーになります。

private int myProp;
public int MyProperty
{
 
                get { return myProp; }
  
protected set { myProp = value; }
}

private int myProp;
public int MyProperty
{
protected get { return myProp; }
protected set { myProp = value; }
}

ただし、get と set 両方にアクセシビリティ修飾子を指定すると、
「アクセシビリティ修飾子は、プロパティまたはインデクサ 'App.MyProperty' の両方のアクセサに指定できません。」 というエラーになります。

デリゲートの共変性と反変性

デリゲートの共変性(Covariance)と反変性(Contravariance)

デリゲートの共変性(Covariance)
継承したデリゲートをベースクラスのデリゲートへ代入できる



class 哺乳類

{
}


class
犬: 哺乳類
{
}


class
Program
{

  // Define the delegate.

  public
delegate 哺乳類 HandlerMethod();

 
public static 哺乳類 FirstHandler()
  {

    return
null;
  }


  public
static 犬 SecondHandler()
  {

    return
null;
  }


  static
void Main()
  {

    HandlerMethod
handler1 = FirstHandler;

   
// SecondHandler は、犬のクラス
    // HandlerMethod は、哺乳類のデリゲート
    // しかし、共変性により、HanderMethod への SecondHandler の代入が可能になる。
    // Covariance allows this delegate.

    HandlerMethod
handler2 = SecondHandler;
  }
}

反変性(Contravariance)
ベースクラスのデリゲートを継承したデリゲートへ代入できる

using System;
using
System.Windows.Forms;
using
System.Diagnostics;

namespace
WindowsFormsApplication1
{
 
public partial class Form1 : Form
  {
   
public Form1()
    {
      InitializeComponent();

     
this.textBox1.KeyDown += this.MultiHandler; // KeyEventArgs
     
this.button1.MouseClick += this.MultiHandler; // MouseEventArgs
    }

   
private void textBox1_KeyDown(object sender, KeyEventArgs e)
    {
     
Trace.WriteLine("textBox1_KeyDown");
    }

   
private void button1_MouseClick(object sender, MouseEventArgs e)
    {
     
Trace.WriteLine("button1_MouseClick");
    }

   
private void MultiHandler(object sender, System.EventArgs e)
    {
     
Trace.WriteLine("MultiHandler");
    }
  }
}

C# 1.0, 1.2 で、同じような実装をすると次のようになる。しかし、これは
「メソッド 'WindowsApplication1.Form1.MultiHandler(object, System.EventArgs)' はデリゲート型 'void System.Windows.Forms.KeyEventHandler(object, System.Windows.Forms.KeyEventArgs)' と一致しません。」
というコンパイルエラーになってしまう。

this.textBox1.KeyDown += new System.Windows.Forms.KeyEventHandler(this.MultiHandler);
this.button1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.MultiHandler);

C# 2.0 では、デリゲートの共変性(Covariance)と反変性(Contravariance) が可能となり、実装の幅が広がっています。

デリゲートの宣言の簡素化

工事中

固定サイズ バッファ

C# 1.0, 1.2 では、C# 構造体が配列要素数を指定した配列を定義できず、C++ スタイルの固定サイズの構造体を宣言するのが困難でした。このため、とくに InterOp の場合に、実装が難しいという問題がありました。

C# V2.0 では、fixed キーワードにより、固定の配列を宣言することができます。

public struct MyArray
{
    public fixed char pathName[128];
}

フレンド アセンブリ

アセンブリの internal なクラスや、メンバに、別のアセンブリからアクセスできます。

ライブラリ cs_friend_assemblies.dll をコンパイルし、
[assembly: InternalsVisibleTo("cs_friend_assemblies_2")]
とすることに
より、cs_friend_assemblies_2.exe or cs_friend_assemblies_2.dll のアセンブリから、internal Class1, internal なメソッド Class2.Test を呼び出すことができるようになる。

// cs_friend_assemblies.cs
// compile with: /target:library
using System.Runtime.CompilerServices;
using
System;
[
assembly: InternalsVisibleTo("cs_friend_assemblies_2")]

// internal by default

class
Class1
{
 
public void Test()
  {
   
Console.WriteLine("Class1.Test");
  }
}


// public type with internal member

public
class Class2
{
 
internal void Test()
  {
   
Console.WriteLine("Class2.Test");
  }
}

もし、[assembly: InternalsVisibleTo("cs_friend_assemblies_2")] を指定しなかったり、呼び出し側のアセンブリ名が"cs_friend_assemblies_2"でなければ、次のようなエラーになり、アクセスができません。

「エラー 1 'Class1' はアクセスできない保護レベルになっています。 」

「エラー 5 'Class2' に 'Test' の定義が含まれておらず、型 'Class2' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。 」

#pragma 警告

#pragma warning を使用すると、特定の警告を有効または無効にできます。

using System;

public
class TestPragma
{
 
static void Main()
  {
   
int x = 0;

    #pragma
warning disable 219
   
int y = 0;

    #pragma
warning restore 219
   
int z = 0;
  }
}

変数 x, z は、次の警告になります。

警告 1 変数 'x' は割り当てられていますが、その値が使用されていません。

警告 2 変数 'z' は割り当てられていますが、その値が使用されていません。

しかし、変数 y は、警告は発生しません。

volatile

volatile キーワードは、lock による排他制御がおこなわれていないが、複数のスレッドからアクセスされる可能性がある変数に対して、JIT コンパイラーに対して、コンパイラによる最適化を行わないように指示します。このため、フィールドには常に最新の値が含まれます。

通常に volatile なしでコンパイルすると、MSIL は、次のようなコードを生成します。

int x = 0;
.field private int32 x

一方、volatile を指定してコンパイルすると、MSIL は次のように変数は揮発性(IsVolatile)であるため、最適化しないように指示されます。

volatile int y = 0;

.field private int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) y

C# 3.5の主な追加機能

LINQ - Language-Integrated Query

.Net Framework 3.5から、LINQ が追加されました。

従来のデータクエリーの問題点

  • クエリは単純な文字列として表され、コンパイル時の型チェックが行えない

  • IntelliSense のサポートは利用できない。

  • SQL データベース、XML ドキュメント、さまざまな Web サービスなど、データ ソースの種類ごとに異なるクエリ言語。

LINQ のメリット

  • 完全な型チェック

  • IntelliSense のサポート

  • データソースによらない統合されたクエリ言語

言語拡張

var   クエリ式

var query =
 
from num in numbers
 
where num > 3
 
orderby num
 
select num;

var   暗黙的に型指定されるローカル変数
       (Implicitly Typed Local Variables)

最適な型をコンパイラが決定した、強く型付けされた変数。

注意: バリアント型や、弱い型付けされた型や、遅延バインディングではない。
注意: varの使用は、コードが理解しづらくなる可能性があるので、必要最小限にしたほうが良い。

var i = 1; // int
var
s = "Hello World"; // string
var expr = // IEnumerable<Customer>
  from
c in customers
  where
c.City == "Tokyo"
  select
c;

var s = "abc";
s = 1;   // エラー 1 型 'int' を型 'string' に暗黙的に変換できません。

コンパイラーが静的に型推論を行ってくれる。この例だと、s に "abc" という文字列を代入しているので、s は string 型であると推論できる。したがって、この変数 s に、1 を代入しようとすると、「型 'int' を型 'string' に暗黙的に変換できません。」 というエラーになる。

var  暗黙に型指定される配列

var a = new[] { 0, 1, 2 };  // 変数 a は整数の配列と型推論される。

var  匿名型 (anonymous type)

var person = new { Name = "Uchukamen", Age = 34 };
int age = person.Age;
string name = person.Name;

オブジェクト初期化子とコレクション初期化子

Customer cust = new Customer { Name = "Tom", Phone = "555-5555" };

namespace ConsoleApplication
{
 
class Program
 
{
    class
Customer
   
{
      public
string Name { get; set; }
      public
string Phone { get; set; }
    }

    static
void Main(string[] args)
    {
      Customer cust = new Customer { Name = "Tom", Phone = "555-5555" };
    }
  }
}

拡張メソッド

拡張メソッドは型に関連付けることができる静的メソッドで、その型のインスタンス メソッドと同じように呼び出すことができる。この機能を使用すると、既存の型を実際に変更しなくても、その型に新しいメソッドを実質的に "追加" できます。標準クエリ演算子は、IEnumerable<(Of <(T>)>) を実装する任意の型で LINQ クエリ機能を実現する拡張メソッドのセットになっている。

詳細については、MSDN の「拡張メソッド (C# プログラミング ガイド)」を参照。

ラムダ式

ラムダ式は、=> 演算子を使用して関数本体から入力パラメータを分離するインライン関数であり、コンパイル時にはデリゲートまたは式ツリーに変換されます。LINQ プログラミングでは、標準クエリ演算子に対する直接メソッド呼び出しを行う場合にラムダ式を使用します。

自動実装プロパティ

次の例のようなプロパティを宣言すると、コンパイラによってプライベートな匿名バッキング フィールドが作成されます。プライベートな匿名バッキング フィールドには、プログラムからはアクセスできません。

例: public string Name {get; set;}

LINQで扱えるデータソース

  • 配列
    int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

  • XML
    XElement contacts = XElement.Load(@"c:\myList.xml");

  • データベース
    DataContext db = new DataContext(@"c:\northwnd.mdf");

LINQ の例

int[] numbers = { 0, 1, 2, 3, 4, 5, 6 };
var
query =
  from
v in numbers
  where
v > 3
  select
v;

foreach
(var i in query)
{
  Console
.WriteLine(i);
}

注意: 遅延実行
var query = で定義するクエリ変数自体が行うのはクエリ コマンドの格納のみです。
実際のクエリの実行は、foreach ステートメントで呼び出されるまで処理が延期されます。

クエリの即時実行

クエリは遅延実行で定義されていますが、Count, Sum などの集計関数によりクエリを即時実行することができます。

int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

var query =
 
from v in numbers
 
where v > 3
 
select v;

Console.WriteLine(query.Sum());

また、ToArray, ToList により、即時実行させることもできます。

int[] array = query.ToArray();
List<int> list = query.ToList();
 

クエリ構文

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

class
LINQQueryExpressions
{
 
static void Main()
  {
   
int[] scores = { 97, 92, 81, 60 };

    IEnumerable
<int> scoreQuery =
     
from score in scores
     
where score > 80
     
select score;

    foreach
(int i in scoreQuery)
    {
     
Console.WriteLine(i + " ");
    }

   
Console.ReadLine();
  }
}

クエリ構文とメソッド構文

通常は、クエリ構文の方が単純で読みやすいため、クエリ構文を使用することをお勧めします。

.NET CLRには、クエリ構文の概念はありません。したがって、クエリ式は、コンパイル時に CLR が理解できる形式、つまりメソッド呼び出しに変換されます。

したがって、次のクエリ構文は、

int[] numbers = { 5, 10, 8, 3, 6, 12};

//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;

次のメソッド構文と同じです。

//Method syntax:
IEnumerable
<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

このメソッド構文には、拡張メソッド (Where と OrderBy)、ラムダ式(num => num % 2 == 0 と、n => n) が使われています。

拡張メソッド(Extension Method)

Where と OrderBy メソッドは、System.Linq 名前空間で定義されているメソッドが拡張されているものです。

独自に拡張メソッドを追加することも可能で、次の例は int に Cube という 3乗するメソッドを拡張した例。

using System;
using
System.Linq;

namespace
CustomExtensions
{
 
public static class MathExtension
  {
   
public static int Cube(this int i)
    {
     
return i*i*i;
    }
  }
}


namespace
Extension_Methods_Simple
{
 
using CustomExtensions;
 
class Program
  {
   
static void Main()
    {
     
int i = 5;

     
int j = i.Cube();
     
Console.WriteLine("{0} の3乗は {1}", i, j);

     
Console.ReadLine();
    }
  }
}

ラムダ式

ラムダ式とは、式とステートメントを含めることができ、計算を実行して単一の値を返す、名前を持たない匿名関数です。これにより、デリゲート型または式ツリー型を作成することができます。
ラムダ式は、デリゲートを作成するための最も便利な方法です。

前の例で、Where(num => num % 2 == 0)  は、条件式 (num % 2 == 0) がインライン引数として Where メソッドに渡されています。ここで、=> が ラムダ演算子です。

ラムダ式には、式形式と、ステートメント形式の2種類があります。 

式形式のラムダ

右辺に式があるラムダ式を式形式のラムダと呼びます。
式形式のラムダは、式ツリーの構築に幅広く使用されます。
式形式のラムダは式の結果を返します。 

書式: (input parameters) => expression


x => x * x
(x, y) => x == y
注意: かっこはラムダの入力パラメータが 1 つの場合のみ省略可能

using System;
using
System.Linq;

namespace
Lambda
{
 
class Program
  {
   
delegate int Calc1(int x);
   
delegate int Calc2(int x, int y);

   
static void Main(string[] args)
    {

      Calc1
calc1 = (x) => x * 2;
      Console.WriteLine(calc1(4));

      //  引数が1つの場合は、括弧を省略可能
     
calc1 = x => x * 3;
      Console.WriteLine(calc1(4));

      Calc2 calc2 = (x, y) => x * y;
      Console.WriteLine(calc2(4,5));

      Console
.ReadLine();
    }
  }
}

ステートメント形式のラムダ

書式: (input parameters) => {statement;}

ステートメント形式のラムダの本体は任意の数のステートメントで構成できます。現実的には、2、3 個以下にします。

using System;
using
System.Linq;

namespace
Lambda
{
 
class Program
  {
   
delegate string StatementLambda(string str);

   
static void Main(string[] args)
    {

     
StatementLambda sLambda = n =>
      {
       
string s = n + " + World";
       
Console.WriteLine(s);
       
return "return value = " + s;
      }

     
string retVal = sLambda("Hello");
     
Console.WriteLine(retVal);

   
  Console.ReadLine();
    }
  }
}

汎用デリゲートを使ったラムダ式

ラムダ式は、計算を実行して単一の値を返すので、汎用デリゲートを活用すると
デリゲートを個別に宣言せずにすむ。
特に、System.Linq 名前空間に存在する型の多くのメソッドには、Func<T, TResult>型が多いため、デリゲートを明示的に宣言することなく、これらのメソッドにラムダ式を渡すことができる。

Func<TResult>
      public delegate TResult Func<TResult>()
      パラメータを受け取らずに、TResult パラメータに指定された型の値を返す

Func<T, TResult>
      public delegate TResult Func<T, TResult>(T arg)
      1 つのパラメータを受け取って TResult パラメータに指定された型の値を返す

同様に、次の汎用デリゲートがある。
Func<T1, T2, TResult>
Func<T1, T2, T3, TResult>
Func<T1, T2, T3, T4, TResult>

//
delegate int MyDeleg(int n);

static void StatementLambdaWithoutGenericDelegate()
{
  MyDeleg myDel = n => n * n;
 
Console.WriteLine(myDel(6));
}

// 汎用デリゲートを使用すると
static void StatementLambdaAndGenericDelegate()
{
  Func<
int, int> myDel = n => n * n;
  
Console.WriteLine(myDel(7));
}

というように、すこし簡潔に書ける。

標準クエリ演算子でのラムダ

ラムダ式

static void LambdaInStandardQueryOperators()
{
  
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

  
foreach (var x in numbers.Where(n => n > 5))
  
  Console.WriteLine(x);
}

// TakeWhile Do Until
static void TakeWhile()
{
  
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

  
foreach (var x in numbers.TakeWhile(n => n < 6))
    
Console.WriteLine(x);
}

System.Linq.Queryable

IQueryable(T) を実装するデータ構造を照会するための一連の static メソッドを提供します。 このメンバーには、Count, Distinct, First, GroupBy, Join, Last, Max, Min, OrderBy, Select, Take, TakeWhile, ThenBy, ThenByDescending, Union, Where などのメソッドが提供されています。

これにより、次のような実装が可能になります。

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

Console
.WriteLine("Count : " + numbers.Count(n => n > 3));

Console
.WriteLine("First : " + numbers.First(n => n > 5));

foreach (int x in numbers.Where(n => n > 5))
 
Console.WriteLine(x);

ここで、次のメソッド構文をクエリ構文に変換すると

IEnumerable<int> query = from i in numbers
                                    
where i > 5
                                    
select i;
foreach (int x in query)
 
Console.WriteLine(x);

となります。これはさらに次のように短く書くことができます。

foreach (int x in from i in numbers where i > 5 select i)
 
Console.WriteLine(x);

OrderBy と OrderByDescending

string[] names = { "Miku", "Ren", "Rin", "Uchukamen"};

var
orderedNames = names.OrderBy(s => s);
foreach (string item in orderedNames)
 
Console.WriteLine(item);

var
orderByDescendingsNames = names.OrderByDescending(s => s);
foreach (string item in orderByDescendingsNames)
 
Console.WriteLine(item);

GroupBy

string[] names = { "Miku", "Ren", "Rin", "Uchukamen"};
var groups = names.GroupBy(s => s.Length, s => s[0]);

foreach (IGrouping<int, char> group in groups)
{
 
Console.WriteLine("Strings of length {0}", group.Key);
 
foreach (char value in group)
   
Console.WriteLine(" {0}", value);
}

LINQ - データソースの利用

XMLデータソース

List から XMLに変換する場合の処理をしようと思うと、XmlDocument, XmlElementを
使用して、次のようになる。

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

namespace ConsoleApplication2
{
    class Program
    {
        class Student
        {
            public string First;
            public string Last;
            public int ID;
            public List Scores;
        }

        static void Main(string[] args)
        {
            List students = new List()       
            {
                new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores = new List{97, 92, 81, 60}},
                new Student {First="Claire", Last="O’Donnell", ID=112, Scores = new List{75, 84, 91, 39}},
                new Student {First="Sven", Last="Mortensen", ID=113, Scores = new List{88, 94, 65, 91}},
            };

            XmlDocument xmlDoc = new XmlDocument();
            XmlElement root = xmlDoc.CreateElement("Root");
            xmlDoc.AppendChild(root);
            foreach (Student std in students)
            {
                XmlElement first = xmlDoc.CreateElement("First");
                first.InnerText = std.First;
                XmlElement last = xmlDoc.CreateElement("Last");
                last.InnerText = std.Last;
                XmlElement id = xmlDoc.CreateElement("ID");
                id.InnerText = std.ID.ToString();
                string scoreStr = String.Format("{0},{1},{2},{3}", std.Scores[0],
                    std.Scores[1], std.Scores[2], std.Scores[3]);
                XmlElement scores = xmlDoc.CreateElement("Score");
                scores.InnerText = scoreStr;

                XmlElement student = xmlDoc.CreateElement("student");
                student.AppendChild(first);
                student.AppendChild(last);
                student.AppendChild(id);
                student.AppendChild(scores);
                root.AppendChild(student);
            }
            xmlDoc.Save("test.xml");

            Console.ReadKey();
        }
    }
}
 

これを LINQ を使うと次のように、かなり見通しが良くなる。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            Test();
        }

        class Student
        {
            public string First;
            public string Last;
            public int ID;
            public List Scores;
        }

        static List students = new List()       
            {
                new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores = new List{97, 92, 81, 60}},
                new Student {First="Claire", Last="O’Donnell", ID=112, Scores = new List{75, 84, 91, 39}},
                new Student {First="Sven", Last="Mortensen", ID=113, Scores = new List{88, 94, 65, 91}},
            };

        static void Test()
        {
            var studentsToXML = new XElement("Root",
                from student in students
                let x = String.Format("{0},{1},{2},{3}", student.Scores[0],
                        student.Scores[1], student.Scores[2], student.Scores[3])
                select new XElement("student",
                    new XElement("First", student.First),
                    new XElement("Last", student.Last),
                    new XElement("Scores", x)
               ) // end "student"
            ); // end "Root"

            Console.WriteLine(studentsToXML);

            Console.ReadKey();
        }
    }
}

注意: let句を使用して、サブ式の結果を格納して後の句で使用することができる。