« 2012年05月 | メイン | 2012年12月 »

2012年11月 アーカイブ

2012年11月13日

[Windows 8][Windows ストア アプリ] Windows 8 の WinRT アプリのインストール方法 - サイドローディング (WinRT アプリ の Windows ストアを介さないインストール方法) 等

Windows 8 の WinRT アプリ (Metroスタイル アプリと呼ばれていたもの、Windows ストア アプリとも *1) のインストール方法についてまとめてみる。

(*1) WinRT アプリには「Windows ストア アプリ」と云う呼び方もある。但し、「Windows ストアにある Windows ストアに WinRT アプリでないデスクトップ アプリがあったらそれは Windows ストア アプリなのか?」とか「Windows ストアを介さない WinRT アプリは Windows ストア アプリなのか?」等の疑問が残り、必ずしも「WinRT アプリ = Windows ストア アプリ」とは云えないかも知れない。(*2)

(*2) かつて「Metro スタイル」と呼ばれていたスタイルは「Microsoft デザイン スタイル」と呼ばれるようになったらしい。一方、WinRT は Windows 8 の新ランタイムで、Windows ストア アプリが主に使用している。

WinRT アプリをインストールする方法は以下の通り。

  1. Windows ストアを介して配布
    1. Windows ストア開発者になるための手順」に従い(Microsoft アカウントと結び付けられた) Windows ストア開発者アカウントを取得する。
    2. マイクソフトとアプリ開発者契約を結ぶ (個人は年間4900円。現在は MSDNサブスクリプションを購入していれば一年分無料。企業は年間9,800円)。
    3. Windows ストアに作成したアプリを提出。
    4. 審査が通り Windows ストアに並んだら、Windows ストアからインストールできる。
  2. 開発者が Visusal Studio からテストのために実行 (Visual Studio から)
    1. Windows ストア開発者アカウントを取得する。
    2. 開発者用ライセンス (無償) をインストールする。
      ※ 上述の Windows ストア開発者アカウントが必要だが、上述のアプリ開発者契約は不要。
      Visual Studio からインストール可能。ない場合は、初回起動時に警告が出るので、その指示に従えばインストールできる。
    3. テストのために起動。テスト用にアプリがインストールされる。
  3. 開発者が Visusal Studio からテストのために実行 (手動インストール)
    1. Windows ストア開発者アカウントを取得する。
    2. 開発者用ライセンス (無償) をインストールする。
      ※ 上述の Windows ストア開発者アカウントが必要だが、上述のアプリ開発者契約は不要。
      ・管理者権限で起動した PowerShell のプロンプトから Show-WindowsDeveloperLicenseRegistration コマンドレットで開発者用ライセンスをインストールできる。
      例.
      > Show-WindowsDeveloperLicenseRegistration
    3. アプリを手動インストールする。
      ・管理者権限で起動した PowerShell のプロンプトから Add-AppxPackage コマンドレットでアプリをインストール。
      例.
      > Add-AppxPackage -Register .\AppxManifest.xml
  4. サイドローディング
    ※ Windows Store を介さずに WinRT アプリをインストールすることができ、この方法をサイドローディング (Sideloading) と云う。
    以下に説明がある。 手順
    1. Windows8 Enterprise Edition または Windows Server 2012 で Active Directoryドメインに参加する。
    2. ドメインのグループ ポリシーでサイドローディングを許可 (グループ ポリシー エディターで「信頼できるすべてのアプリのインストールを許可する」を有効にする)。
    3. 信頼されたルート証明書機関からの証明書でアプリを署名
    4. アプリをインストール
      ・管理者権限で起動した PowerShell のプロンプトから Add-AppxPackage コマンドレットでアプリをインストール。
    ※ Windows 8 Enterprise や Windows Server 2012 以外のエディションの Windows や、ドメインに参加していない Windows 8 Enterprise PC でサイドローディングを有効にするには、サイドローディング プロダクト キーのライセンス認証を行う必要がある。

2012年11月14日

[Event] VSUG Day 2012 Winter

2012.12.15(Sat) マイクロソフト品川ビル31F

▼日付: 2012年12月15日(土) 受付9:30~予定
▼会場: マイクロソフト品川オフィス 31F
▼参加費用: 無料 (VSUG 会員登録が必要となります)

詳細/申し込み: http://vsug.jp/tabid/228/EventID/22/Default.aspx

2012年11月15日

[Windows 8/10] 日本語キーボードのノートPCで外付け英語キーボードを使用する方法

【Windows 8/10】 日本語キーボードのノートPCで外付け英語キーボードを使用する方法

日本語キーボードのノートPCに USB接続で外付け英語キーボードを使用したいことがある。

私の場合で云えば、プログラミングを行う際等に愛用の Happy Hacking Keyboard を使いたい場合がある。
(英語配列の Happy Hacking Keyboard は私にはとても具合が良い。 キー数が少なくコンパクトなところが良い。
勿論、日本語のローマ字入力にも支障がないし、Mac でも Windows マシンでも問題ない。 後ろのディップスイッチを切り替えることで、スペース キーの左右のキーを Mac では【command キー】、Windows マシンでは【Windows キー】として使うことができるのだ)

ところが、その儘の日本語 Windows 8/10 で英語キーボードを外付けすると、日本語キーボードとはキー配列が異なる為に不都合が生じる。
【@】や【"】を始めとする幾つかの記号の配置が異なるのだ。

キーボードを換える度に再起動せずに、この不都合を解消する方法がほしい。
USJP Pro」 と云う専用のソフトウェアがあるが、Windows 8/10 に正式対応していないし、有料だ。

そこで、レジストリを書き換える方法を試した。
この方法では、一度設定してしまえば、英語キーボードを繋ぐ度に設定したり再起動したりする必要がない。

※ レジストリ・エディタの使用には注意が必要だ。試される方は、自己責任でお願いしたい。

■ 試した環境

今回は以下の環境で試した。

  • 日本語キーボード ノートPC: DELL LATITUDE E4310
  • 外付け英語キーボード (USB接続): PFU Happy Hacking Keyboard Professional (PD-KB300NL)
  • OS: Windows 8/10 (64bit/日本語)
  • 日本語入力: MS-IME
試した環境
試した環境

■ 設定方法

実際の設定方法は以下の通り。レジストリの書き換えと言語の追加を行った。

  1. レジストリの書き換え
    先ずは「レジストリの書き換え」を行った。
    1. レジストリ エディターの起動
      1. 【Windows キー】+【X キー】で、メニューを表示
      2. メニューから「ファイル名を指定して実行」を選ぶ
      3. 「regedt32」と入力し、「OK」ボタンを押す
        「ファイル名を指定して実行」で「regedt32」と入力
        「ファイル名を指定して実行」で「regedt32」と入力
      4. ユーザー アカウント制御のメッセージ ボックスが表示されたら、「OK」ボタンを押す
        ユーザー アカウント制御のメッセージ ボックス
        ユーザー アカウント制御のメッセージ ボックス
      5. レジストリ エディターが起動する
    2. レジストリ エディターでの書き換え
      1. レジストリ エディターのメニューの「編集」-「検索」を選ぶ
      2. 「検索する値」に「LayerDriver JPN」と入力し、「次を検索」ボタンを押す
        「LayerDriver JPN」の検索
        「LayerDriver JPN」の検索
      3. 見つかった「LayerDriver JPN」をダブル クリックし、値のデータを「kbd106.dll」から「kbd101.dll」に変更し、「OK」ボタンを押す
        「LayerDriver JPN」の値のデータを「kbd101.dll」に変更
        「LayerDriver JPN」の値のデータを「kbd101.dll」に変更
      4. レジストリの変更を反映させるため、PC を再起動する
  2. 言語の追加
    次に「言語の追加」を行った。
    1. 【Windows キー】+【X キー】で、メニューを表示
    2. メニューから「コントロール パネル」を選ぶ
    3. 「コントロール パネル」で「時計、言語、および地域」の「言語」を選択
      「コントロール パネル」-「時計、言語、および地域」-「言語」を選択
      「コントロール パネル」-「時計、言語、および地域」-「言語」を選択
    4. 「言語の設定の変更」が表示されるので、「言語の追加」を選択
      「言語の設定の変更」
      「言語の設定の変更」
    5. 「言語の追加」が表示されるので、「English (英語)」を選び、「開く」ボタンを押す
      「言語の追加」
      「言語の追加」
    6. 「地域のバリエーション」が表示されるので、「英語(米国)」を選び、「追加」ボタンを押す
      「地域のバリエーション」
      「地域のバリエーション」
    7. 「言語」で「日本語」の下に「English (United States)」が表示される
      「言語」が追加された
      「言語」が追加された
    8. ちなみに、この時「English (United States)」の「オプション」は以下のようになっている
      「English (United States)」の「オプション」
      「English (United States)」の「オプション」

■ 設定後の操作方法

以上で、英語キーボードを繋いでも繋いでもノート PC の日本語キーボードでは、日本語の配列で入力ができ、英語キーボードを繋げば、英語キーボードでは英語の配列で入力できるようになった。
先に述べたように、以降、英語キーボードを繋ぐ度に設定したり再起動したりする必要はない。

それぞれのキーボードで、以下のように【英数字直接入力】と【かな漢字変換】を切り替えられるようになった。

○ 【英数字直接入力】と【かな漢字変換】への切り替え方法
  • ノートPC上の日本語キーボード: 【Alt キー】+【半角/全角キー】
  • 外付け英語キーボード: 【Alt キー】+【` キー】(【~ キー】)

また、以下のようにすると【日本語キーボード入力モード】と【英語キーボード入力モード】を切り替えることができる。
尤も、こちらは余り使わないかも知れない。

○ 【日本語キーボード入力モード】と【英語キーボード入力モード】への切り替え方法
  • 【Alt キー】+【Shift キー】
  • 【Windows キー】+【スペース キー】で、以下のメニューとともに切り替えられる
    「Windows キー + スペース キー」で出るメニュー
    【Windows キー】+【スペース キー】で出るメニュー

続きを読む "[Windows 8/10] 日本語キーボードのノートPCで外付け英語キーボードを使用する方法" »

2012年11月20日

[Windows 8][Windows Store アプリ][XAML] Windows Store アプリと Windows Phone アプリ、Silverlight アプリ、WPF アプリでソースコードを共通化する方法に関する記事

Windows Store アプリと Windows Phone アプリ、Silverlight アプリ、WPF アプリは、どれも XAML を使って開発することができ、共通する部分も多い。

そこで、これらでソースコードを共通化する方法に関する記事を紹介する。

先ずは @IT の岩永 信之氏の記事から。"Portable Class Library" と呼ばれるマルチ プラットフォーム クラス ライブラリによるソースコードの共通化、MVVMパターンによるビューとモデルの分離方法について、判りやすく書かれている。

  • XAMLファミリ共通開発のすゝめ - @IT

続いて Windows Store アプリ開発に関する記事を多く書かれている山本 康彦氏のスライドから。

こちらでは、スライドの30ページ目から Portable Class Library について書かれている。

このスライドでは以下の記事が引用されている:

この記事では、MVVM パターンを用いている。各プラットフォーム用に View を分け、Model と ViewModel を Portable Class Library で共通化することを推奨している。

Portable Class Library に関して幾つか他の記事を紹介する:

プラットフォーム間での XAML の違いについては、以下の記事も参考になる:

2012年11月28日

[C#][ラムダ式][LINQ][式木] 匿名メソッドとラムダ式の違い

Expression

この記事では、匿名メソッドとラムダ式の意味の違いについて考えてみたい。

■ 同じように使える匿名メソッドとラムダ式

匿名メソッドとラムダ式は、同じように使うことができる場面が多い。

例えば、以下のようなデリゲートを引数にとるメソッドがあったとして、

using System;
using System.Collections.Generic;

static class Enumerable
{
    // 述語に基づいて値のシーケンスをフィルター処理
    public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        foreach (var item in source) {
            if (predicate(item))
                yield return item;
        }
    }
}

この predicate には、通常のメソッドも匿名メソッドもラムダ式も渡すことができ、いずれも同じ結果となる。

using System;

class Program
{
    static bool IsEven(int number)
    {
        return number % 2 == 0;
    }

    static void Main()
    {
        var numbers = new[] { 3, 7, 1, 6, 4, 5 };

        // 普通のメソッドをデリゲートに入れて渡す例 
        var evenNumbers1_1 = numbers.Where(new Func<int, bool>(IsEven));
        // 上に同じ
        var evenNumbers1_2 = numbers.Where(IsEven);
        // 匿名メソッドを使った例
        var evenNumbers2 = numbers.Where(delegate(int number) { return number % 2 == 0; });
        // ラムダ式を使った例
        var evenNumbers3 = numbers.Where(number => number % 2 == 0);
    }
}

いずれの場合も、同じ型のデリゲートとして渡されることになるのだ。

以下の例でも同じ型のデリゲートとして受けられることが判る。

     Func<int, bool> delegate1_1 = new Func<int, bool>(IsEven);
    Func<int, bool> delegate1_2 = IsEven; // 上と同じ
    Func<int, bool> delegate2 = delegate(int number) { return number % 2 == 0; };
    Func<int, bool> delegate3 = number => number % 2 == 0;

上の例では、一行目 delegate1_1 の例と二行目 delegate1_2 の例は全く同じ意味だ。書き方が違うだけの、単なる糖衣構文 (syntax sugar) に過ぎない。

では、三行目の匿名メソッドを使った delegate2 の例 と四行目のラムダ式を使った delegate3 の例も、単なる糖衣構文 (syntax sugar) なのだろうか?

確かに、ラムダ式を使った方が、型推論の恩恵を存分に受けられ、書き方がぐっとシンプルになる。だが、書き方だけの違いなのだろうか?

■ 匿名メソッドとラムダ式で挙動が異なる例

今度は、両者で違いが出る例を見てみよう。

LINQ to SQL を使った例だ。

予め SQL Server に Employee というシンプルなテーブルを用意した。

Employee テーブル

次に、Visual Studio でプロジェクトを作り、EmployeeDataClasses.dbml という名前で「LINQ to SQL クラス」を追加した。

「LINQ to SQL クラス」を追加
・LINQ to SQL - ラムダ式で書いた場合

では、ラムダ式を使ってデータ アクセスを行う例から見てみよう。

// LINQ to SQL の例 - ラムダ式で書いた場合
using LambdaExpressionSample; // EmployeeDataClassesDataContext の名前空間
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        var dataContext = new EmployeeDataClassesDataContext();
        dataContext.Log = Console.Out; // 発行された SQL をモニターする為に、コンソールに出力

        var data1 = dataContext.Employee.Where(employee => employee.Name.Contains("山"));
        var data2 = data1.OrderBy(employee => employee.Name);
        var data3 = data2.Select(employee => employee.Name);

        data3.ToList().ForEach(Console.WriteLine);
    }
}

この例で発行された SQL は、以下の通り。

SELECT [t0].[Name]
FROM [dbo].[Employee] AS [t0]
WHERE [t0].[Name] LIKE @p0
ORDER BY [t0].[Name]
-- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [%山%]

三行に分けて書いているのに、SQL の発行は data3.ToList() のときの一回だけだ。data1、data2、data3 と三回も戻り値を受けているのに、ToList() されるまで評価が遅延されている訳だ。

SQL の発行は一回だけだが、where は WHERE、OrderBy は ORDER BY、Select で Name だけなのは SELECT で Name だけと、ちゃんと狙い通りのものが発行されているのが判る。

LINQ to SQL (や Entity Framework) の凄いところだ。

因みに、上ではメソッド構文を使って書いているが、クエリ構文を使って書くこともできる。

以下の二つは意味的には同じで、糖衣構文 (syntax sugar) だ。

// メソッド構文
var data3 = dataContext.Employee
            .Where(employee => employee.Name.Contains("山"))
            .OrderBy(employee => employee.Name)
            .Select(employee => employee.Name);
// クエリ構文
var data3 = from employee in dataContext.Employee
            where employee.Name.Contains("山")
            orderby employee.Name
            select employee.Name;
・LINQ to SQL - 匿名メソッドで書いた場合

次に、この例を匿名メソッドに置き換えてやってみよう。ラムダ式が匿名メソッドの単なる糖衣構文 (syntax sugar) なら同じ結果になる筈だ。

// LINQ to SQL の例 - 匿名メソッドで書いた場合
using LambdaExpressionSample;
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        var dataContext = new EmployeeDataClassesDataContext();
        dataContext.Log = Console.Out; // 発行された SQL をモニターする為に、コンソールに出力

        var data1 = dataContext.Employee.Where(delegate(Employee employee) { return employee.Name.Contains("山"); });
        var data2 = data1.OrderBy(delegate(Employee employee) { return employee.Name; });
        var data3 = data2.Select(delegate(Employee employee) { return employee.Name; });

        data3.ToList().ForEach(Console.WriteLine);
    }
}

結果はこうなった。

SELECT [t0].[Id], [t0].[Name]
FROM [dbo].[Employee] AS [t0]

ラムダ式の場合と大きく異なる。

WHERE による行の絞り込みも SELECT による列の絞り込みも ORDER BY による並べ替えも全然反映されていない。

つまり、この例では、LINQ to SQL で単純に Employee テーブルの全列、全行を取ってきて、後は LINQ to Object でオンメモリで処理している訳だ。

これでは全く非効率だ。ラムダ式を使うべき、と云うことになろう。

・LINQ to SQL - ラムダ式と匿名メソッドを混在させた場合

序でに、両者を混在させた例も見ておこう。

// LINQ to SQL の例 - ラムダ式と匿名メソッドを混在させた場合
using LambdaExpressionSample; // EmployeeDataClassesDataContext の名前空間
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        var dataContext = new EmployeeDataClassesDataContext();
        dataContext.Log = Console.Out; // 発行された SQL をモニターする為に、コンソールに出力

        var data1 = dataContext.Employee.Where(employee => employee.Name.Contains("山"));
        var data2 = data1.OrderBy(delegate(Employee employee) { return employee.Name; });
        var data3 = data2.Select(employee => employee.Name);

        data3.ToList().ForEach(Console.WriteLine);
    }
}

結果はこうなった。

SELECT [t0].[Id], [t0].[Name]
FROM [dbo].[Employee] AS [t0]
WHERE [t0].[Name] LIKE @p0
-- @p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [%山%]

SQL に反映されているのは、ラムダ式を使った Where までで、匿名メソッドより後ろは LINQ to Object で処理されているのが判る。

この例で、ラムダ式は匿名メソッドと意味的に同じものではない、ということが判った。

■ 匿名メソッドとラムダ式の違い

では、両者はどう意味が違うのだろうか。

上の LINQ to SQL の例の Where の部分を比べてみよう。

    // ラムダ式を使った例
    var data1 = dataContext.Employee.Where(employee => employee.Name.Contains("山"));
    // 匿名メソッドを使った例
    var data1 = dataContext.Employee.Where(delegate(Employee employee) { return employee.Name.Contains("山"); });

Visual Studio を使って、両者の Where の部分を右クリックし、「定義へ移動」してみよう。

一見同じ Where メソッドを呼んでいるようだが、異なった Where メソッドを呼んでいることが判るだろう。

// ラムダ式を使った例から呼ばれる Where メソッド
namespace System.Linq
{
    public static class Queryable
    {
        public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
    }
}
// 匿名メソッドを使った例から呼ばれる Where メソッド
namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
    }
}

匿名メソッドの方は Func<TSource, bool>、即ちデリゲートだが、ラムダ式の方は Expression と云う違うもので受けている。

参考までに、Func と Expression の定義は以下のようになっている。

// Func はデリゲート
namespace System
{
    public delegate TResult Func<in T, out TResult>(T arg);
}
// Expression はデリゲートではない
namespace System.Linq.Expressions
{
    public sealed class Expression<TDelegate> : LambdaExpression;
}

ラムダ式は、あくまでも「式」として渡されているのだ。

対して、匿名メソッドは Expression として扱うことができない。

試してみよう。

using LambdaExpressionSample; // EmployeeDataClassesDataContext の名前空間
using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        // デリゲートとして使う分には、どちらでも OK
        Func<Employee, bool> delegate1 = employee => employee.Name.Contains("山");
        Func<Employee, bool> delegate2 = delegate(Employee employee) { return employee.Name.Contains("山"); };

        // だが、Expression として使えるのはラムダ式の方だけ
        Expression<Func<Employee, bool>> expression1 = employee => employee.Name.Contains("山");
        //Expression<Func<Employee, bool>> expression2 = delegate(Employee employee) { return employee.Name.Contains("山"); }; // コンパイル エラーになる
    }
}

匿名メソッドの方は、「匿名メソッド式を式のツリーに変換することはできません」と云うコンパイル エラーとなる。

「匿名メソッド式を式のツリーに変換することはできません」と云うコンパイル エラー

即ち、ラムダ式を Expression として扱っている場合は、匿名メソッドは代わりにはならない、ということになる。

ラムダ式は匿名メソッドの糖衣構文 (syntax sugar) などではないのだ。

一般的には、ラムダ式を使うことで書き方もシンプルになることだし、匿名メソッドは使わずにラムダ式で書くようにした方が良いだろう。

■ 今回のまとめ

今回は、匿名メソッドとラムダ式の意味の違いについて考察した。

ラムダ式はデリゲートのみならず、式としても扱うことができる、と云うことだ。

LINQ to SQL のような LINQ プロバイダーでは、ときにラムダ式を式として扱うことが大切だろう、と想像できる。

だが、それ以外のプログラミングでラムダ式を式として扱って便利なことってあるんだろうか?

と云う訳で、次回は、ラムダ式を Expression として扱うと便利な例について書く予定だ。

2012年11月29日

[C#][ラムダ式][式木] Expression として扱えるラムダ式と扱えないラムダ式

Expression

前回、「匿名メソッドとラムダ式の違い」と云う記事で、匿名メソッドとラムダ式の意味の違いについて考えた。

それについて、少し補足しておきたい。

「ラムダ式を Expression として扱っている場合は、匿名メソッドは代わりにはならない」と述べたが、ラムダ式であれば何でも Expression として扱える訳ではないのだ。

■ Expression として扱えるラムダ式

ラムダ式として足し算を行うだけの (x, y) => x + y と云うシンプルなものを用意し、これを Expression で受けてみる。

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        Expression<Func<int, int, int>> expression = (x, y) => x + y;
    }
}

これは特に問題ない。

■ Expression として扱えないラムダ式

ラムダ式の => より右の部分には式だけでなくステートメントのブロックも置くことができる。

このラムダ式の => より右の部分を、式ではなく、ステートメントのブロックに変えてみよう。
例えば、(x, y) => { return x + y; } とし、これを Expression で受けてみる。どうなるだろうか?

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        Expression<Func<int, int, int>> expression = (x, y) => { return x + y; };
    }
}

すると今度は、「ステートメント本体を含むラムダ式は、式のツリーに変換できません」というコンパイル エラーになる。

「ステートメント本体を含むラムダ式は、式のツリーに変換できません」というコンパイル エラー

つまり、ラムダ式の => の右の部分が式である場合は Expression として扱えるが、ステートメントのブロックである場合は Expression として扱えないのだ。

■ 考察

「式でないものは式として扱えない」と云う、云わば当たり前の結果だ。

ところで、F# や Scala、Haskell などの関数型プログラミングをより得意とする言語では、if 等多くのものが式として扱える。

それらと比較すると、C# ではまだまだ式として書ける範囲は少ない。
例えば、if は文だ (勿論 C# でも三項演算子で書けるものは式で表現できる)。
だが、C# でもっと多くのものが、F# や Scala、Haskell などの言語のように文でなく式で表せるようになれば、益益 Expression で扱える範囲が増える、と云うことになるだろう。

■ 今回のまとめ

今回は、前回の記事の補足を軽く行った。

今後、ラムダ式を Expression として利用する例を何回かに分けて、紹介していく予定だ。

次回は、先ず Expression そのものへの理解を深めるために Expression の構造を調べてみたいと思う。

2012年11月30日

[C#][ラムダ式][式木] Expression の構造を調べてみる

Expression

匿名メソッドとラムダ式の違い」と云う記事で、匿名メソッドとラムダ式の意味の違いについて考えた。

「ラムダ式を Expression として扱っている場合は、匿名メソッドは代わりにはならない」と述べたが、ラムダ式を Expression として扱う例について、これから数回に分けて書いていきたい。

ラムダ式を Expression として扱う例を挙げる前に、今回は、先ずは Expression 自体について理解を深めたいと思う。

Expression はどのような構造をしているのだろうか? 中がどうなっているのかを見てみることにしよう。

■ Expression をデバッガーで観察してみよう

手始めに、Visual Studio のデバッガーを用いて Expression の中を覗いてみる。

ラムダ式として足し算を行うだけの (x, y) => x + y と云うシンプルなものを用意し、これを Expression で受ける。

using System;
using System.Linq.Expressions;

class Program
{
    static void Main()
    {
        Expression<Func<int, int, int>> expression = (x, y) => x + y;
    }
}

これをデバッグ実行して、デバッガーで expression の中を覗いてみる。

デバッガーで expression の中を覗いてみる 1

Body、Name、NodeType、Parameters、Type 等の各プロパティとその値が見えているのが判るだろう。

Name はなく、NodeType は Lambda だ。名前がないと云うことと、式の種類がラムダ式であることを表している。
Type は Func`3、つまり TResult Func<in T1, in T2, out TResult> になっている。

Body の中を見てみよう。

デバッガーで expression の中を覗いてみる - Body

ラムダ式の中の => より後ろの x + y の部分であることが判る。引数の x が見えている。NodeType は Add で足し算の式であることを表している。
Type は Int32、つまり int だ。
Left は x、Right は y となっていて、二項演算である足し算の左オペランドが x、右オペランドが y であることを表している。

更に Left の中を見てみる。

デバッガーで expression の中を覗いてみる - Body - Left

Name は x、Type は Int32 つまり int。
NodeType は Parameter で式の種類が引数であることを表している。

続いて Parameters。

デバッガーで expression の中を覗いてみる - Parameters

Parameters は要素数 2 のコレクションになっているようだ。ラムダ式の中の => より前の部分の引数を表していることが判る。

Parameters の一つ目の要素を見てみよう。

デバッガーで expression の中を覗いてみる - Parameters の一つ目の要素

引数の x が見えている。NodeType は Parameter つまり式の種類は引数、Type は Int32 つまり int だ。

■ Expression はツリー構造

上の例では、expression のNodeType は Lambda であり、この式の種類がラムダ式であることを表していた。

その他にも、幾つかの式が出てきている。

例えば、Body の部分。ラムダ式の中の => より後ろの x + y の部分だが、ここも式であり、式の種類は二項演算の足し算の式であった。

デバッガーでよく見てみると、これらの式も Expression であることが判る。

Expression はその中に再帰的に Expression を持つことでツリー構造になっているようだ。

■ Expression の種類

Expression には幾つかの種類があることも判る。

ラムダ式、二項演算の式、などだ。

実は、Expression クラスには沢山の派生クラスがあり、それぞれが様様な式の種類を表している。

以下を参照してほしい:

その一部を示すと、こんな感じだ。

Expression クラスの派生クラス 種類 NodeType プロパティの値
NodeType NodeType の説明
LambdaExpression ラムダ式 Lambda ラムダ式
BinaryExpression 二項演算式 Add 足し算
AddChecked オーバーフロー チェックを行う足し算
Subtract 引き算
SubtractChecked オーバーフロー チェックを行う引き算
Multiply 掛け算
MultiplyChecked オーバーフロー チェックを行う掛け算
Divide 割り算
Modulo 剰余演算
Power 累乗
UnaryExpression 単項演算式 Negate 単項マイナス演算
NegateChecked オーバーフロー チェックを行う単項マイナス演算
UnaryPlus 単項プラス演算
Not ビット補数演算または論理否定演算
Convert キャスト演算
ConvertChecked チェック付きキャスト演算
TypeAs as による型変換
ArrayLength 配列の長さを取得する演算
Quote 定数式

■ Expression のツリー構造を見てみよう

それでは、Expression のツリー構造を実際に見てみよう。

ツリー構造になった Expression を再帰的に辿りながら表示していくクラスを作ってみた。

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;

static class EnumerableExtensions
{
    // コレクションの各要素に対して、指定された処理をインデックス付きで実行
    public static void ForEach<TItem>(this IEnumerable<TItem> collection, Action<TItem, int> action)
    {
        int index = 0;
        foreach (var item in collection)
            action(item, index++);
    }
}

// Expression の中身をダンプ (ラムダ式、二項演算式、単項演算式以外は簡易表示)
static class ExpressionViewer
{
    // Expression の中を (再帰的に) 表示
    public static void Show(this Expression expression, int level = 0)
    {
        if (expression as LambdaExpression != null)
            // ラムダ式のときは詳細に表示
            ShowLambdaExpression((LambdaExpression)expression, level);
        else if (expression as BinaryExpression != null)
            // 二項演算のときは詳細に表示
            ShowBinaryExpression((BinaryExpression)expression, level);
        else if (expression as UnaryExpression != null)
            // 単項演算のときは詳細に表示
            ShowUnaryExpression((UnaryExpression)expression, level);
        else if (expression != null)
            // それ以外も沢山あるが、今回は省略してベース部分だけ表示
            ShowExpressionBase(expression, level);
    }

    // Expression のベース部分を表示
    static void ShowExpressionBase(Expression expression, int level)
    {
        ShowText(string.Format("☆Expression: {0}", expression), level);
        ShowText(string.Format("ノードタイプ: {0}", expression.NodeType), level + 1);
    }

    // LambdaExpression (ラムダ式) の中を (再帰的に) 表示
    static void ShowLambdaExpression(LambdaExpression expression, int level)
    {
        ShowExpressionBase(expression, level);
        ShowText(string.Format("名前: {0}", expression.Name), level + 1);
        ShowText(string.Format("戻り値の型: {0}", expression.ReturnType), level + 1);
        ShowParameterExpressions(expression.Parameters, level + 1); // 引数のコレクション
        ShowText(string.Format("本体: {0}", expression.Body), level + 1);
        expression.Body.Show(level + 2); // 本体を再帰的に表示
    }

    // BinaryExpression (二項演算式) の中を (再帰的に) 表示
    static void ShowBinaryExpression(BinaryExpression expression, int level)
    {
        ShowExpressionBase(expression, level);
        ShowText(string.Format("型: {0}", expression.Type), level + 1);
        ShowText(string.Format("左オペランド: {0}", expression.Left), level + 1);
        expression.Left.Show(level + 2); // 左オペランドを再帰的に表示
        ShowText(string.Format("右オペランド: {0}", expression.Right), level + 1);
        expression.Right.Show(level + 2); // 右オペランドを再帰的に表示
    }

    // UnaryExpression (単項演算式) の中を (再帰的に) 表示
    static void ShowUnaryExpression(UnaryExpression expression, int level)
    {
        ShowExpressionBase(expression, level);
        ShowText(string.Format("型: {0}", expression.Type), level + 1);
        ShowText(string.Format("オペランド: {0}", expression.Operand), level + 1);
        expression.Operand.Show(level + 2); // オペランドを再帰的に表示
    }

    // 引数の式のコレクションを表示
    static void ShowParameterExpressions(ReadOnlyCollection<ParameterExpression> parameterExpressions, int level)
    {
        ShowText("引数群", level);
        if (parameterExpressions == null || parameterExpressions.Count == 0)
            ShowText("引数なし", level);
        else
            parameterExpressions.ForEach((parameterExpression, index) => ShowParameterExpression(parameterExpression, index, level + 1));
    }

    // 引数の式の中を表示
    static void ShowParameterExpression(ParameterExpression parameterExpression, int index, int level)
    {
        ShowText(string.Format("引数{0}", index + 1), level + 1);
        ShowExpressionBase(parameterExpression, level + 1);
        ShowText(string.Format("引数の型: {1}, 引数の名前: {2}", parameterExpression.NodeType, parameterExpression.Type, parameterExpression.Name), level + 2);
    }

    // 文字列をレベルに応じてインデント付で表示
    static void ShowText(string itemText, int level)
    {
        Console.WriteLine("{0}{1}", Indent(level), itemText);
    }

    // インデントの為の文字列を生成
    static string Indent(int level)
    {
        return level == 0 ? "" : new string(' ', (level - 1) * 4 + 1) + "|-- ";
    }
}

この ExpressionViewer クラスを使って、先程の expression の中を見てみよう。

class Program
{
    public static void Main()
    {
        Expression<Func<int, int, int>> expression = (x, y) => x + y;
        ((Expression)expression).Show();
    }
}

実行してみると、こうなる。

☆Expression: (x, y) => (x + y)
 |-- ノードタイプ: Lambda
 |-- 名前:
 |-- 戻り値の型: System.Int32
 |-- 引数群
         |-- 引数1
         |-- ☆Expression: x
             |-- ノードタイプ: Parameter
             |-- 引数の型: System.Int32, 引数の名前: x
         |-- 引数2
         |-- ☆Expression: y
             |-- ノードタイプ: Parameter
             |-- 引数の型: System.Int32, 引数の名前: y
 |-- 本体: (x + y)
     |-- ☆Expression: (x + y)
         |-- ノードタイプ: Add
         |-- 型: System.Int32
         |-- 左オペランド: x
             |-- ☆Expression: x
                 |-- ノードタイプ: Parameter
         |-- 右オペランド: y
             |-- ☆Expression: y
                 |-- ノードタイプ: Parameter

「☆Expression」とあるところが式 (Expression) だ。ツリー構造になっている。

「引数群」のところでは、x と y がそれぞれ引数という種類の式としてネストしている。

「本体」は x + y の部分で、足し算を表す式としてネストしている。
その足し算の左オペランドである x と右オペランドである y が、それぞれ引数タイプの式として更にネストしている。

次に、ちょっとだけラムダ式を複雑にしてみよう。(x, y) => x + y を (x, y, z) => x + y + z に変えてみる。

こうだ。

class Program
{
    public static void Main()
    {
        Expression<Func<int, int, int, int>> expression = (x, y, z) => x + y + z;
        ((Expression)expression).Show();
    }
}

さて、結果はどう変化するだろうか。

☆Expression: (x, y, z) => ((x + y) + z)
 |-- ノードタイプ: Lambda
 |-- 名前:
 |-- 戻り値の型: System.Int32
 |-- 引数群
         |-- 引数1
         |-- ☆Expression: x
             |-- ノードタイプ: Parameter
             |-- 引数の型: System.Int32, 引数の名前: x
         |-- 引数2
         |-- ☆Expression: y
             |-- ノードタイプ: Parameter
             |-- 引数の型: System.Int32, 引数の名前: y
         |-- 引数3
         |-- ☆Expression: z
             |-- ノードタイプ: Parameter
             |-- 引数の型: System.Int32, 引数の名前: z
 |-- 本体: ((x + y) + z)
     |-- ☆Expression: ((x + y) + z)
         |-- ノードタイプ: Add
         |-- 型: System.Int32
         |-- 左オペランド: (x + y)
             |-- ☆Expression: (x + y)
                 |-- ノードタイプ: Add
                 |-- 型: System.Int32
                 |-- 左オペランド: x
                     |-- ☆Expression: x
                         |-- ノードタイプ: Parameter
                 |-- 右オペランド: y
                     |-- ☆Expression: y
                         |-- ノードタイプ: Parameter
         |-- 右オペランド: z
             |-- ☆Expression: z
                 |-- ノードタイプ: Parameter

一段ネストが深くなったのがお判りだろうか。

x + y + z のところが (x + y) + z、即ち「『x + y という足し算の式』を左オペランドとし、z を右オペランドとする足し算の式」になっている。

では、更にネストを深くする為に、もっと複雑なラムダ式を使ってみよう。

「デリゲートを入力としデリゲートを出力とするラムダ式」でやってみる。

例えばこんなやつ。

    Func<Func<double, double, double>, Func<int, int, int>> convertFunc = func => ((x, y) => (int)func((double)x, (double)y));

余談だが、一応このラムダ式について説明してみる。

このラムダ式は、「int 二つを引数とし int を返すメソッド」を「double 二つを引数とし double を返すメソッド」に変換する。
double を対象とした足し算を int を対象とした足し算に変換したり、double を対象とした引き算を int を対象とした引き算に変換したりできることになる。

余り意味のない例だが、こんな感じだ。

using System;

static class Program
{
    static double Add(double x, double y)
    {
        return x + y;
    }

    static double Subtract(double x, double y)
    {
        return x - y;
    }

    public static void Main()
    {
        Func<Func<double, double, double>, Func<int, int, int>> convertFunc = func => ((x, y) => (int)func((double)x, (double)y));
        int answer1 = convertFunc(Add )(1, 2);
        int answer2 = convertFunc(Subtract)(4, 3);
    }
}

このラムダ式を ExpressionViewer クラスを使ってダンプしてみよう。

class Program
{
    public static void Main()
    {
        Expression<Func<Func<double, double, double>, Func<int, int, int>>> expression = func => ((x, y) => (int)func((double)x, (double)y));
        ((Expression)expression).Show();
    }
}

結果は下のようになる。

☆Expression: func => (x, y) => Convert(Invoke(func, Convert(x), Convert(y)))
 |-- ノードタイプ: Lambda
 |-- 名前:
 |-- 戻り値の型: System.Func`3[System.Int32,System.Int32,System.Int32]
 |-- 引数群
         |-- 引数1
         |-- ☆Expression: func
             |-- ノードタイプ: Parameter
             |-- 引数の型: System.Func`3[System.Double,System.Double,System.Double], 引数の名前: func
 |-- 本体: (x, y) => Convert(Invoke(func, Convert(x), Convert(y)))
     |-- ☆Expression: (x, y) => Convert(Invoke(func, Convert(x), Convert(y)))
         |-- ノードタイプ: Lambda
         |-- 名前:
         |-- 戻り値の型: System.Int32
         |-- 引数群
                 |-- 引数1
                 |-- ☆Expression: x
                     |-- ノードタイプ: Parameter
                     |-- 引数の型: System.Int32, 引数の名前: x
                 |-- 引数2
                 |-- ☆Expression: y
                     |-- ノードタイプ: Parameter
                     |-- 引数の型: System.Int32, 引数の名前: y
         |-- 本体: Convert(Invoke(func, Convert(x), Convert(y)))
             |-- ☆Expression: Convert(Invoke(func, Convert(x), Convert(y)))
                 |-- ノードタイプ: Convert
                 |-- 型: System.Int32
                 |-- オペランド: Invoke(func, Convert(x), Convert(y))
                     |-- ☆Expression: Invoke(func, Convert(x), Convert(y))
                         |-- ノードタイプ: Invoke

「引数群」のところは、単に一つのデリゲートになっている。

「本体」のところは、ラムダ式がネストしている。
そして、ネストしたラムダ式は、通常のラムダ式と同じようにツリー構造に展開されている。

■ 今回のまとめ

と云う訳で、今回は Expression の構造を見てみた。

次回以降で、愈愈ラムダ式を Expression として利用する例を紹介したい。

About 2012年11月

2012年11月にブログ「プログラミング C# - 翔ソフトウェア (Sho's)」に投稿されたすべてのエントリーです。過去のものから新しいものへ順番に並んでいます。

前のアーカイブは2012年05月です。

次のアーカイブは2012年12月です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

Powered by
Movable Type 3.35