プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

IQueryable な Twitter のタイムライン クラスと LINQ プロバイダー

C#

C# Advent Calendar 2014」の12日目の記事。

前の記事 ← → 次の記事

以前、「[C#][式木][LINQ] Hokuriku.NET C# 勉強会『C# 式木』(2014-10-26、金沢) のスライド公開」で、IQueryable な LINQ について解説した。

  1. LINQ to Objects 復習
  2. IQueryable<T>
  3. 式木 (Expression Tree)
  4. 式木メタ プログラミング
  5. LINQ プロバイダー

本記事では、その中の IQueryable なサンプルを補足する。

IQueryable な LINQ の中はどのようになっているのだろうか。

試しに少し実装してみることで、LINQ について理解を深めよう。

IEnumerable と IQueryable

[C#][ラムダ式][LINQ][式木] 匿名メソッドとラムダ式の違い」で紹介したように、匿名メソッドdelegate としてしか使えないが、ラムダ式delegate としても式木としても使うことができる。

[C#][ラムダ式][式木] Expression として扱えるラムダ式と扱えないラムダ式」で紹介したように、ラムダ式であれば必ず式木として使うことができるわけではない。

※ クエリ構文は、「式木として扱えるラムダ式」の糖衣構文。つまり、式木を扱うことになる。

参考: LINQ でのクエリ構文とメソッド構文 (C#) - MSDN

LINQ の中には、次の二つの種類のライブラリがある。

  1. delegate を引数にしたもの
  2. 式木を引数にしたもの

LINQ to Objects などは前者で処理され、LINQ to SQLLINQ to Entities などは後者だ。

IQueryable なものを作ってみよう

今回は、IQueryable な Twitter のライムライン クラスを作ろうとしてみる。

先ずは IQueryable なクラス QueryableTweets。

※ IQueryable なだけでは OrderBy の対象となることができないので、ここでは IQueryable からの派生で OrderBy 可能な IOrderedQueryable を用いることにする。

// QueryableTweets.cs

using System.Linq;

// IOrderedQueryable<string> な QueryableTweets
// まだ IOrderedQueryable インタフェイスを実装してないのでコンパイル エラー
public class QueryableTweets<TElement> : IOrderedQueryable<TElement>
{}

ここから、Visual Studio でインタフェイスの実装を行うと次のようになる。

// QueryableTweets.cs

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

// IOrderedQueryable<string> な QueryableTweets
// Visual Studio でインタフェイスを実装した直後
public class QueryableTweets<TElement> : IOrderedQueryable<TElement>
{
    public Type ElementType
    {
        get { throw new NotImplementedException(); }
    }

    public Expression Expression
    {
        get { throw new NotImplementedException(); }
    }

    public IQueryProvider Provider
    {
        get { throw new NotImplementedException(); }
    }

    public IEnumerator<TElement> GetEnumerator()
    { throw new NotImplementedException(); }

    IEnumerator IEnumerable.GetEnumerator()
    { throw new NotImplementedException(); }
}

IQueryable は IEumerable から派生している。そのため IEumerable のメンバーである GetEnumerator() を実装する必要がある。

その他に、ElementType、Expression、Provider というプロパティを実装しなければならない。

 実装を進めていこう。このクラスの実装はそれほど大変ではない。

Provider プロパティのために IQueryProvider インタフェイスを持つクラスを用意する必要があるが、ここでは、それを仮に TwitterQueryProvider クラスとしておこう。 TwitterQueryProvider クラスは後述する。

// QueryableTweets.cs

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

// IOrderedQueryable<string> な QueryableTweets
public class QueryableTweets<TElement> : IOrderedQueryable<TElement>
{
    public Type ElementType
    {
        get { return typeof(TElement); }
    }

    public Expression     Expression { get; set; }
    public IQueryProvider Provider   { get; set; }

    public QueryableTweets()
    {
        Provider = new TwitterQueryProvider(); // IQueryProvider インタフェイスを実装したクラス。後述。
        Expression = Expression.Constant(this);
    }

    public IEnumerator<TElement> GetEnumerator()
    { return ((IEnumerable<TElement>)Provider.Execute(Expression)).GetEnumerator(); }

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

IQueryProvider なもの (LINQ プロバイダー) を作ろうとしてみよう

続いて、上記 QueryableTweets で使うための、IQueryProvider なものの実装だ。

これは、LINQ プロバイダーと呼ばれるもので、式木としてのクエリーを解釈する。

こちらの実装は大変だ。

クラス名を TwitterQueryProvider として、IQueryProvider を実装していこう。

// TwitterQueryProvider.cs

using System.Linq;

// LINQ プロバイダーの実験用
// まだ IQueryProvider インタフェイスを実装してないのでコンパイル エラー
public class TwitterQueryProvider : IQueryProvider
{}

ここから、Visual Studio でインタフェイスの実装を行うと次のようになる。

// TwitterQueryProvider.cs

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

// LINQ プロバイダーの実験用
// Visual Studio でインタフェイスを実装した直後
public class TwitterQueryProvider : IQueryProvider
{
    public IQueryable CreateQuery(Expression expression)
    { throw new NotImplementedException(); }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    { throw new NotImplementedException(); }

    public object Execute(Expression expression)
    { throw new NotImplementedException(); }

    public TResult Execute<TResult>(Expression expression)
    { throw new NotImplementedException(); }
}

少し実装を進めてみる。

// TwitterQueryProvider.cs

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

// LINQ プロバイダーの実験用
public class TwitterQueryProvider : IQueryProvider
{
    IQueryable IQueryProvider.CreateQuery(Expression expression)
    { return null; }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    { return new QueryableTweets<TElement> { Provider = this, Expression = expression }; }

    public TResult Execute<TResult>(Expression expression)
    { return default(TResult); }

    public object Execute(Expression expression)
    {
        // ここで式木を解釈して、コレクションを作って返す
        return null; // とりあえずは仮に null を返すだけにしておく
    }
}

この中で、ポイントとなるのは Execute メソッドだ。

この Execute メソッドには、式木が渡ってくる。この式木を解釈してやって、そこからコレクションとしての結果を返してやれば良い。

ここは後で実装することにして、とりあえずは null を返すだけにしておく。

ExpressionVisitor の派生クラスで Visitor パターンによる式木の解釈

LINQ プロバイダーの Execute メソッドでの式木を解釈だが、それには、ExpressionVisitor というクラスが使える。

ExpressionVisitor から派生することで、Visitor パターンによる解析が可能となる。

参考: ExpressionVisitor クラス - MSDN

LINQ のための式木をきちんと解釈するのは、かなり大変なことだ。

ここでは、ごく一部の構文にだけ注目して、そこのみに対応することにする。

取り敢えずの最低限のサンプル コード、Where(text => text.Contains("C#")) の形にのみ対応してみる。

尚、この中では、Twitter のタイムラインを取得する TwitterTimeline クラスを使っているが、 TwitterTimeline クラスは後述する。

// TwitterExpressionVisitor.cs

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

// Visitor パターンで式木を解析して検索用文字列を取り出し、それを使って Twitter のタイムラインを取得する
public class TwitterExpressionVisitor : ExpressionVisitor
{
    public IEnumerable<string> Statuses { get; private set; }

    // 取り敢えずの最低限のサンプル コード
    // Where(text => text.Contains("C#")) の形にのみ対応してみる
    protected override Expression VisitMethodCall(MethodCallExpression expression)
    {
        // もし Where メソッドを呼ぶ式だったら
        if (expression.Method.Name == "Where") {
            // Where メソッドの第二引数であるラムダ式を取り出す
            var lambdaExpression = (LambdaExpression)((UnaryExpression)(expression.Arguments[1])).Operand;
            // そのラムダ式の Body 部を取り出す
            var bodyExpression = lambdaExpression.Body as MethodCallExpression;
            // もし Contains メソッドを呼ぶ式で
            if (bodyExpression != null && bodyExpression.Method.Name == "Contains") {
                // その引数が定数式だったら
                var constantExpression = bodyExpression.Arguments[0] as ConstantExpression;
                if (constantExpression != null) {
                    // その定数の値を検索文字列とし
                    var searchText = constantExpression.Value as string;
                    if (searchText != null)
                        // TwitterTimeline クラス (後述) を使って、タイムラインからその検索文字列にあたる Status を取得しておく
                        Statuses = new TwitterTimeline().Filter(searchText).Select(status => status.Text);
                }
            }
        }
        return base.VisitMethodCall(expression);
    }
}

TwitterTimeline クラスによる Twitter タイムラインの取得

次に、Twitter のタイムラインを取得するためのダミー クラス TwitterTimeline を用意する。

実際に Twitter のタイムラインを取得するコードを用意すれば良いわけだが、今回は説明の簡略化のために CoreTweet というライブラリとダミー コードを用いることにする。

CoreTweet は、Visual Studio から NuGet でインストールできる。

NuGet で CoreTweet のインストール
NuGet で CoreTweet のインストール

ダミー コードは次の通り。

// TwitterTimeline.cs

using CoreTweet; // Twitter のタイムライン取得用
using System.Collections;
using System.Collections.Generic;
using System.Linq;

// Twitter のタイムライン取得用
// CoreTweet ( https://github.com/CoreTweet/CoreTweet/wiki/Home(%E6%97%A5%E6%9C%AC%E8%AA%9E) ) を利用
// NuGet でインストールできる
// Twitterの開発者向けサイト "Twitter Developers" ( https://dev.twitter.com ) にアプリケーションの登録をし、
// Consumer Key、Consumer Secret、Access Token、Access Secret を取得するなどすれば、
// 実際に Twitter のタイムラインから取得することも可能
class TwitterTimeline : IEnumerable<Status>
{
    public IEnumerable<Status> Filter(string searchText)
    {
        // ダミー実装
        // 実際には、ここで searchText にマッチする Status のみを取ってくるのが良い
        return this.Where(status => status.Text.Contains(searchText));
    }

    public IEnumerator<Status> GetEnumerator()
    {
        // "Twitter Developers" に登録し、キーやトークンを取得すれば、実際に Twitter のタイムラインを取れる
        //var tokens = CoreTweet.Tokens.Create("[Your Consumer Key]", "[Your Consumer Secret]",
        //                                     "[Your Access Token]", "[Your Access Secret]");
        //return tokens.Statuses.HomeTimeline().GetEnumerator();

        // ダミー実装
        // 実際には Twitter のタイムラインを取ってくる
        yield return new Status { Text = "C# で CoreTweet を使って Twitter のタイムラインを取得してみた" };
        yield return new Status { Text = "式木いじりは茨の道" };
        yield return new Status { Text = "C#LINQ を使う" };
        yield return new Status { Text = "Hokuriku,NET C# 式木" };
    }

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

LINQ プロバイダー TwitterQueryProvider への組み込み

では、TwitterQueryProvider に TwitterExpressionVisitor を組み込んでみよう。

Execute メソッドの中で、TwitterExpressionVisitor の Visit を呼ぶ。 すると、TwitterExpressionVisitor が式木を解釈し、結果を Statuses に入れるので、それを返せば OK だ。

// TwitterQueryProvider.cs

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

// LINQ プロバイダーの実験用
public class TwitterQueryProvider : IQueryProvider
{
    IQueryable IQueryProvider.CreateQuery(Expression expression)
    { return null; }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    { return new QueryableTweets<TElement> { Provider = this, Expression = expression }; }

    public TResult Execute<TResult>(Expression expression)
    { return default(TResult); }

    public object Execute(Expression expression)
    {
        // ここで式木を解釈して、コレクションを作って返す
        var expressionVisitor = new TwitterExpressionVisitor();
        expressionVisitor.Visit(expression);
        var statuses = expressionVisitor.Statuses;
        return statuses.AsQueryable<string>();
    }
}

テスト

使ってみよう。

コンソール アプリケーションの Main から使用してみる。

// Program.cs

using System;
using System.Linq;

class Program
{
    static void Main()
    {
        IQueryable<string> query1 = new QueryableTweets<string>();
        IQueryable<string> query2 = query1.Where(text => text.Contains("C#"));

        // ここまでは、式木をつくっているだけ
        Console.WriteLine(query2.Expression);
        Console.WriteLine();

        // 下の foreach 中で実際に値を item に取り出そうとすると、
        // 1. TwitterQueryProvider の Execute にその式木が渡され、
        // 2. TwitterExpressionVisitor でそれが解析される中で、
        // 3. TwitterTimeline がタイムラインの取得を行う

        foreach (var item in query2)
            Console.WriteLine(item);
    }
}

実行結果は、次の通りだ。

value(QueryableTweets`1[System.String]).Where(text => text.Contains("C#"))

C# で CoreTweet を使って Twitter のタイムラインを取得してみた
C#LINQ を使う
Hokuriku,NET C# 式木

始めに式木が表示され、その後で、クエリーの結果 "C#" が含まれる Status が三行表示された。

このサンプルコードの範囲ではうまく動いたようだ。

■ 今回のまとめ

今回は、Hokuriku.NET C# 勉強会『C# 式木』 で説明した内容を補足した。

IQueryable なものを書き LINQ に対応させるのはかなり大変だが、その意味するところだけでも理解していけば、LINQ について理解を深めることができるように思う。

MVP ComCamp -2nd Round- 開催中

MVP ComCamp -2nd Round- Nov 17 - Nov 21, 2014 が開催されている。

初日に開発者向けセッションを担当 (トラック 2 - Day 1 (2014/11/17): Learn フェイズ)。

発表した資料を公開。

関連記事

「Windows Server 2012 R2 と Windows Server 2003 の混在環境でのコンピューター アカウントのパスワードを変更した後にログオンできない」現象の対処法

【現象】

Windows Server 2003 で構成された Active Directory ドメインに Windows Server 2012 R2 のドメイン コントローラーを追加すると、追加後、2 ヶ月程度経過した後に、ドメインのメンバー、ドメイン コントローラーでログオンができなくなるという現象が発生することがあります。

 

【解決方法】

Windows Server 2012 R2 をドメイン コントローラーとして追加する前に修正プログラムを適用します。

追加作業をすでに完了している場合には、修正プログラムを Windows Server 2012 R2 のドメイン コントローラーに適用し、ドメイン メンバー (ドメイン コントローラーを含む) 全てを再起動します。

 

具体的な対処方法は下記の通りです。

 

現象が発生してしまった場合には、対象のコンピューターを再起動します。

再起動することで、起動時に対応した暗号化キーを生成するため、問題が解消します。

 

問題が発生するのを未然に防ぐためには、次の対応を実施します。

 

1. KB2989971 を全ての Windows Server 2012 R2 のドメイン コントローラーに適用します。

 

Windows Server 2012 R2 と Windows Server 2003 の混在環境でのコンピューター アカウントのパスワードを変更した後にログオンできない

http://support.microsoft.com/kb/2989971

 

2. 全てのドメイン コントローラーに対する修正プログラムの適用処理 (再起動を含む) が完了後に、ドメイン コントローラーを含む全メンバー コンピューターを再起動します。

 

*  KB 2989971 の修正プログラムを適用するためには、下記の 2 点の条件が必要です。

・ KB 2919355 が適用されている事

・ Active Directory ドメインサービスがインストールされている事

  ※ 役割が追加されていれば、必ずしもドメインコントローラーに昇格している必要はありません。

  ※ KB2919355 は Windows Update にて配信されているプログラムとなります。

    こちらを Windows Update から適用したうえで、以下の修正プログラムを適用ください。

 

Windows RT 8.1、8.1 の Windows および Windows Server 2012 の R2 の更新プログラム: 2014 年 4 月

http://support.microsoft.com/kb/2919355/ja

 

【その他】

・ すでに Windows Server 2003 のドメイン コントローラーが降格していてもこの問題は発生する可能性があります。

 

・ Windows Server 2012 R2 のドメイン コントローラーが存在しており、ログオンができないメンバー上でシステム イベントログにMicrosoft-Windows-Security-Kerberos の ID 4 のイベントが記録されている場合には、この問題に合致している可能性が高いと判断できます。

 

・ Windows Server 2012 R2 に Active Directory ドメイン サービスをインストールし、ドメイン コントローラーへの昇格を実施する前に修正プログラムを適用することをお勧めします。

そうすれば、この問題の発生を未然に防ぐために全メンバー コンピューターの再起動は不要です。

 

 

なお、Windows サポート チームがブログ に詳細を掲載していますので、併せてご参照いただけますと幸いです。

『ラムダ式でステップアップ! C#のプログラムから汎用的なアルゴリズムを切り出すことで、LINQについての理解を深めよう』 - CodeZine

algorithm.png

[C++] 『ラムダ式でステップアップ! C++のプログラムから汎用的なアルゴリズムを切り出し利用してみよう』 - CodeZine の続き。

C# によるプログラミングの記事を投稿。

「本稿では、C#のプログラムをリファクタリングして、汎用的なアルゴリズムを切り出し、利用する流れをステップバイステップで解説します。また、C# 3.0で採用されたラムダ式がそれを利用するのに、いかに便利なのか、どういう仕組みなのか、を紹介します。それにより、LINQ(Language INtegrated Query:統合言語クエリ)についての理解も深めていただけると思います。」

『Windows 8開発ポケットリファレンス』

本日は、『Windows 8開発ポケットリファレンス』についてのご紹介です。

『Windows 8開発ポケットリファレンス』
タイトル Windows 8 開発ポケットリファレンス
著者 WINGSプロジェクト 阿佐志保,森島政人,飯島聡,土井毅,花田善仁 著,山田祥寛 監修
定価 定価(本体2,880円+税)
発売日 2014年2月19日発売
サイズ 四六判/496ページ
ISBN 978-4-7741-6296-6
Webサイト

特長

  • 本書では、Windows 8 以降で動作するストアアプリを作成するとき、作りたい機能から逆引きで作り方を学べます。
  • Windows 8.1 に対応しています。8.1 だけで 8 に対応していないものはそれが明記されています。
  • ストアアプリならではの、デスクトップアプリケーションとは異なる作り方を、きちんと説明しています。
  • 必要な部分だけのシンプルなソースコードです。
  • XAML に慣れていない人にも分かりやすく書かれています。
  • 新しい API である WinRT についての豊富なサンプル

逆引きとして便利に使えるだけではありません。

本書は順に、

  1.  開発環境の準備の仕方
  2.  画面の作り方と様々なコントロールを使うサンプル
  3.  応用的な画面の作り方
  4.  ストレージや様々なデバイスの使い方
  5. アプリの連携の方法
  6. その他の便利な機能
  7. 非同期プログラミングのやり方

という構成になっています。後ろに行くほど応用的な内容となっているため、順に読み進め、サンプルを打ち込んでいくと、自然に Windows 8 アプリが作れるようになっています。
新しい技術を習得するには、先ず慣れることです。そのためには、動作するサンプルを実際に書いて動かしてみることが効果的です。次には、様々な機能を入れた作りたいアプリケーションを作ってみること。
そのどちらにも有効です。

ストアアプリ開発の初学者から使える良書です。他に同様の書籍は見当たりません。お勧めです。

『ラムダ式でステップアップ! C++のプログラムから汎用的なアルゴリズムを切り出し利用してみよう』 - CodeZine

algorithm.png

C++ によるプログラミングの記事を投稿。

「本稿では、C++のプログラムをリファクタリングして、汎用的なアルゴリズムを切り出し、利用する流れをステップバイステップで解説します。また、C++11で採用されたラムダ式がそれを利用するのに、いかに便利なのかを紹介します。」