かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

C#3.0の新機能を試す(LINQ,ラムダ式,暗黙的型付け, etc...)

Visual Studio Orcas Beta1が出たので、VC# Orcas Express Editionを入れてみた!
Vistaにもすんなり入りました。
ということで新機能を実験!!!

XAML

WPFアプリケーションを作ろうとしたら、このinstallationではサポートされてないって言われたorz
楽しみにしてたのに!!!


ということでこれはスルー


暗黙的型付け

この機能をフル活用すると、RubyJavaScriptみたいなLL言語みたいに、型名を一切記述しないでプログラムを書くことが出来ることもあるという機能。
たとえば…

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

namespace CS3Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var i = 0;
            Console.WriteLine(i);
            Console.WriteLine(i.GetType());
        }
    }
}

Mainの中のiは、型を宣言してないです。
これを実行すると下記のような結果になる。

0
System.Int32

つまり、型が明らかな場合は「宣言にわざわざ書かなくても面倒見てやるよ」っていうこと。


これは、配列でも言える。
配列の場合は下記のようになる。

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

namespace CS3Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var array = new[] { 0, 1, 2 };
            Console.WriteLine(array.GetType());
            foreach (var i in array)
            {
                Console.WriteLine("{0}: {1}", i.GetType(), i);
            }
        }
    }
}

これの実行結果を示す。

System.Int32[]
System.Int32: 0
System.Int32: 1
System.Int32: 2

結構強力に型を推論してくれている。
Haskellみたいだ。



イニシャライザ

クラスの初期化関係の機能。
今まで、newしたときにプロパティの初期値を設定したい!というためだけに、引数ありのコンストラクタを作ってきた。

例えば、下記のようなageとnameをもつPersonクラスがあるとする。

    class Person
    {
        private int age;
        public int Age
        {
            get { return age; }
            set { age = value; }
        }

        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
    }

これを作って名前と年齢を設定するには、下記のようにnewした後に設定しないといけなかった。

Person p = new Person();
p.Name = "太郎";
p.Age = 26;

もしくは、別途コンストラクタを用意してnew Person("太郎", 26)と書けるようにするかといった感じ。


コンストラクタを用意するのは、「人は絶対に名前と年齢があるはずだ!」というのを強要することで、人を扱うところでは名前と年齢があるものだという前提で扱えばいいというのが楽チンなのでとても素敵だ。
でも、そんなの強要するまでもない時にプロパティの初期化のためだけのコンストラクタを用意するのは非常にめんどくさい。
長くなったけどイニシャライザを使うと、newの後にプロパティの初期化が簡単に出来るようになる。

    class Program
    {
        static void Main(string[] args)
        {
            var p = new Person { Name = "太郎", Age = 26 }; // ここね!
            Console.WriteLine("{0}: {1}才", p.Name, p.Age);
        }
    }

書き方は、new 型名{ プロパティ名 = 値, ... }になる。
これは便利。



匿名型

今までは、classを定義してから使うというのが当たり前だった。
当然これはいいことだと思う。
どんなのかわからないまま使うのは大変。
特に大勢でやるときにはとても大変。


でも、一箇所くらいでしか使わない値を入れるためだけのものに対してもclassを定義してから使うのは非常にめんどくさいし、classがいっぱいできて非常にうざったい。
Dictionaryに入れるのもいいけど、これはこれでメンドクサイ。
そんな時に使えるのが匿名型。
さっき紹介したイニシャライザで型名の部分を省略して書くだけでOK。
下記のようになる。

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

namespace CS3Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var p = new { Name = "太郎", Age = 12 };
            Console.WriteLine("{0}: {1}才", p.Name, p.Age);
            // 気になるので型名を出してみる
            Console.WriteLine(p.GetType());
        }
    }
}

実行結果は下記のようになる。

太郎: 12才
<>f__AnonymousType0`2[System.String,System.Int32]

ちょっとした値をまとめて入れる型を定義するのにすごい便利。
実行結果を見てわかるように、コンパイラが適当に型をこしらえてくれるようだ。


もちろん匿名型の配列もOK

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

namespace CS3Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var ps = new[]{
                         new {Name = "太郎", Age = 26},
                         new {Name = "二郎", Age = 3}
                     };
            foreach (var p in ps)
            {
                Console.WriteLine("{0}: {1}才", p.Name, p.Age);
            }
        }
    }
}

実行すると下記のようになる。

太郎: 26才
二郎: 3才

ふむふむ。
すごいのは、Visual Studioがちゃんとインテリセンスを出してくれるところ。
インテリセンスが効かなかったらRubyとかJavaScriptのようなLL系の言語をエディタで書いてるのと変わらんからね。
タイプミスとかしたらきっちり怒ってくれるのがGOOD。


ラムダ式

C#2.0の匿名デリゲートでも楽になった〜!って感動してました。
delegate(int x, int y) { return x + y; }みたいに書けるんですから。
でも匿名デリゲートの中身があまり長くなるのは嫌いなので、長いものはちゃんとメソッドに書いたりしていると…
匿名デリゲートで書くのは、「ちょっとした条件判断してboolを返す」とか「ちょっとした計算をして返す」とかいうものになってくる。
となると、delegateって打ったりreturnを打ったりするのがめんどい。
そういう不満を持ってた人には朗報です。


ラムダ式は、下記のようにして書けます。

(引数リスト) => 式;

シンプル。
引数リストの中身が、1つだけの時は括弧は省略できるみたいです。
あと、引数リストの型が明らかな場合は型名を省略して書けるみたい。


例は下記のような感じ。リストから偶数だけ抜き取って出力する。

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

namespace CS3Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            // そうそうコレクションも配列みたいに初期化出来るようになったの!
            List<int> l = new List<int> { 1, 2, 3, 4, 5 };

            // 偶数だけを抜き出して、コンソールに出力
            l.FindAll(i => i % 2 == 0).ForEach(i => Console.WriteLine(i));
        }

    }
}

実行結果は、簡単すぎるので省略します。


自動プロパティ

今まで何度同じようなコードを書いてきた(正確には、コードスニペットにまかせてたけど)だろうか。
下記のようなコード

public string name;
public string Name
{
  get { return name; }
  set { name = value; }
}

もう、めんどくてめんどくて…
それが簡単に書けるようになりました。

public string Name { get; set; }

何だかabstractなプロパティと間違えてしまいそうな危険性はあるけど、とっても楽に書けます。
何か特殊なことをしたいときは、中身書けばOK!
でも、そうじゃないことの方がとても多いので多用しそうです。

拡張メソッド

拡張メソッドは、既存のクラス(インターフェイス)にメソッドを追加(したように)見せることが出来る機能です。
とりあえずサンプル!

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

namespace CS3Sample
{
    // 何もメソッドを持たないクラス
    class Sample
    {
    }

    // staticなクラスで
    static class SampleExtensions
    {
        // 引数のthisがポイント!
        public static void Foo(this Sample sample)
        {
            Console.WriteLine("SampleExtensions::Foo");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Sample s = new Sample();
            // こういう風に呼べちゃう!
            s.Foo();
        }
    }

}

実行すると下記のようになる。

SampleExtensions::Foo

メソッドが追加された!?


さて、この拡張メソッドからはprivateなフィールドとかにはアクセスできません。

class Sample
{
  // privateなフィールド
  private int field;
}

static class SampleExtensions
{
  public static void Foo(this Sample sample)
  {
    sample.field = 100; // コンパイルエラー!
  }
}

なので、今まで文字列操作のユーティリティをStringUtilとかいうstaticクラスにしていたのを拡張メソッドを使ってstringのインスタンスメソッドであるかのように使えるようになる。
なるけど…。
人と名前かぶったらどうなるんだろう?


ということで、別々のnamespaceに同じ名前の拡張メソッドを用意してみた。

namespace A
{
    static class AStringExtensions
    {
        public static void Foo(this string str) 
        {
            Console.WriteLine("A::Foo");
        }
    }
}

namespace B
{
    static class BStringExtensions
    {
        public static void Foo(this string str) 
        {
            Console.WriteLine("B::Foo");
        }
    }
}

この状態でビルドしたら「エラーになるだろう」と思ってたけどビルドが通った…?
んじゃstringのFooを呼んだらどうなるんだろ???

namespace CS3Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            string s = "Hello";
            s.Foo(); // エラー!
        }
    }

}

エラーにはなったけど、どうやら思ったのと違うエラーになった。
エラーの内容は「Fooなんて拡張メソッドはない」というような感じだった。
そうか、usingしてやらなきゃ駄目なんだ。
ということでusing A;を追加して実行!

A::Foo

お〜っnamespace Aの拡張メソッドが呼ばれた。
じゃぁusing Bを追加すると…?

The call is ambiguous between the following methods or properties: 'B.BStringExtensions.Foo(string)' and 'A.AStringExtensions.Foo(string)'	

どうやらバッティングしちゃう。(当然か)
拡張メソッドの名前がバッティングした場合は、おとなしく普通にstaticメソッドのノリで呼び出すしかなさそう?
ここら辺はどうなんだろう???


さて、この拡張メソッドには引数も追加できる。
最初のthisキーワードつきの引数に続けて引数をつけるだけ。
stringを拡張する意味がないけど、こんなこと出来るよってことで引数あり版のサンプル。

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

namespace CS3Sample
{
    static class StringExtensions
    {
        public static int Add(this string s, int x, int y)
        {
            return x + y;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // 足し算!
            Console.WriteLine("Hello".Add(10, 20));
        }
    }

}

ちゃんと動きます。




さて、この拡張メソッドはインターフェイスに対してもメソッドを追加出来たりします。

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

namespace CS3Sample
{
    interface IHoge
    {
        void Foo();
    }
    
    static class HogeExtensions
    {
        // IHogeの拡張メソッド
        public static void Boo(this IHoge hoge)
        {
            Console.WriteLine("Boo!!");
            hoge.Foo();
            Console.WriteLine("Boo!!");
        }
    }

    class Hoge : IHoge
    {
        public void Foo()
        {
            Console.WriteLine("Hoge::Foo");
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            IHoge hoge = new Hoge();
            // 拡張メソッド呼べる!
            hoge.Boo();
        }
    }

}

これは便利!!!
あるインターフェイスを実装してさえいれば、色々な便利機能を使えるとかいうことが出来る。
現に、System.Linq.EnumerableはIEnumerableに対する拡張メソッドをいっぱい用意している。
ToDictionaryやToListやToArrayやetc....
なので、自分でIEnumerableを拡張したクラスを用意すると便利メソッドが最初からいっぱいついてくる!
下記のように!

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

namespace CS3Sample
{
    class MyEnumerable : IEnumerable<int>
    {
        private int[] array = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        public IEnumerator<int> GetEnumerator()
        {
            return new MyEnumerator(array);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        private class MyEnumerator : IEnumerator<int>
        {
            // 省略
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            MyEnumerable myEnume = new MyEnumerable();
            // 定義してないはずなのにCountやAverageが使える!
            Console.WriteLine(myEnume.Count());
            Console.WriteLine(myEnume.Average());
        }
    }
}

実行してみるとちゃんと結果が出る。

10
5.5

Rubyのmoduleチックな使い方とかが出来そうです。



LINQ

LINQは個人的に好きです。
今まで説明してきた言語の拡張も、すべてこのためにあるんじゃないか!って感じです。
どんなのかというと、SQLライクな文法で配列やコレクション(IEnumerableを実装したクラス)に対して問い合わせを行うことが出来る。


「例えば1〜10の配列の偶数だけを10倍した結果を返す。」というのは下記のように書ける。

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

namespace CS3Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            var array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            var vals = from i in array
                       where i % 2 == 0
                       select i * 10;
            foreach (var val in vals)
            {
                Console.WriteLine(val);
            }
        }
    }

}

LINQが無かったらforeachでぐるぐるまわしてごにょごにょしてって書かないといけなかったのがすっきり書けるようになる。
groupbyやorderbyもできるので、SQLやったことある人なら問題なく使えると思う。
唯一違うのは、select句が最後にあるくらい。
でも、SQL書くときに頭の中では最後にselect句書いてた(俺は)からLINQのほうが本来あるべき書き順なのかな〜と思ったりします。

XElement

XElementというものが追加されてた。

// <hoge />
XElement elm = new XElement("hoge");

// <hoge attr="value" />
XElement elm = new XElement("hoge", new XAttribute("attr", "value"));

// <hoge>
//   <foo>value</foo>
// </hoge>
XElement elm = new XElement(
                       "hoge", 
                       new XElement("foo", "value"));

XMLが簡単に組み立てれるようになってる。
XElementのコンストラクタは可変長引数なので、XElementを沢山渡してやることも出来る。


これとLINQを組み合わせると、データを加工してXMLに出力するという処理が簡単に出来る!
例えば、下記のような従業員と部署情報がある。

// 従業員クラス
class Employee
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int DeptID { get; set; }
}

// 部署クラス
class Department
{
    public int ID { get; set; }
    public string Name { get; set; }
}
// 部署データを用意
var departments = new Department[] { 
                    new Department{ID = 0, Name = "人事部"},
                    new Department{ID = 1, Name = "総務部"},
                    new Department{ID = 2, Name = "情報システム部"}
                  };
// 従業員データを用意
var employees = new Employee[]{
                    new Employee{ID = 0, Name = "太郎", DeptID = 0},
                    new Employee{ID = 1, Name = "二郎", DeptID = 1},
                    new Employee{ID = 2, Name = "三郎", DeptID = 2},
                    new Employee{ID = 3, Name = "四郎", DeptID = 0}
                };

これから、従業員のIDと名前と所属部署の名前をXML形式で吐き出すには下記のようなプログラムを書けばいい。

// 従業員データと部署データをjoinしてXML形式で吐き出す
XElement elm = new XElement("従業員一覧", 
                       from employee in employees
                       join department in departments on employee.DeptID equals department.ID
                       select new XElement("従業員",
                            new XElement("ID", employee.ID),
                            new XElement("Name", employee.Name),
                            new XElement("DeptName", department.Name)));

// ちょいと保存してみるか
StringWriter sw = new StringWriter();
elm.Save(sw);

// 標準出力へ出力
Console.WriteLine(sw);

これを実行するとこんなXMLが出力される。

<?xml version="1.0" encoding="utf-16"?>
<従業員一覧>
  <従業員>
    <ID>0</ID>
    <Name>太郎</Name>
    <DeptName>人事部</DeptName>
  </従業員>
  <従業員>
    <ID>1</ID>
    <Name>二郎</Name>
    <DeptName>総務部</DeptName>
  </従業員>
  <従業員>
    <ID>2</ID>
    <Name>三郎</Name>
    <DeptName>情報システム部</DeptName>
  </従業員>
  <従業員>
    <ID>3</ID>
    <Name>四郎</Name>
    <DeptName>人事部</DeptName>
  </従業員>
</従業員一覧>

このXMLを出力するプログラムがここまでシンプルに書けるとは…