Hatena::ブログ(Diary)

当面C#と.NETな記録 このページをアンテナに追加 RSSフィード

2007/12/31 (月)

[] LINQ to Object のイディオム その3 : シーケンス生成  LINQ to Object のイディオム その3 : シーケンス生成を含むブックマーク  LINQ to Object のイディオム その3 : シーケンス生成のブックマークコメント

「標準クエリ演算子は、"シーケンス" を操作します。ある型 T に対してインターフェイス IEnumerable<T> を実装するオブジェクトはすべて、その型のシーケンスと見なされます。」.NET 標準クエリ演算子より引用。

シーケンスの「生成演算子」あたりがお題。

冒頭の資料で挙げられてる生成演算子は、Range, Repeat, Empty の3つ。これだけだと記事にするまでもないので、ネタコードで遊んでるうちに気づいた手軽な生成方法を改めて書いときます(これ→id:siokoshou:20071129)。

シーケンスとは IEnumerable<T> なので、最も手軽な生成方法は配列やコレクションを作ってしまうこと。IEnumerable<T> はデータ構造も手続きも同じように扱えてしまう器用さがいいですね。「技術的には簡単だけど、その効果は深遠なもの」ってやつ*1

おもしろいことに文字列も文字のシーケンスです。一文字一文字について何かするなら LINQ が使えます。

using System;
using System.Linq;

class Program
{
  static void Main()
  {
    var seq1 = new[] { 1, 2, 3 }.Concat( new[] { 2007 } );
    var seq2 = Enumerable.Repeat( 10, 2 ); // 10, 10
    var seq3 = Enumerable.Range( 20, 3 ); // 20, 21, 22
    var seq4 = Enumerable.Empty<int>();
    var seq = seq1.Concat( seq2 ).Concat( seq3 ).Concat( seq4 );

    seq.ToList().ForEach( Console.WriteLine );
    Console.WriteLine();

    // siokoshou の中に aiueo は何回出てくるか?
    var q= "siokoshou".Join( "aiueo", c => c, c => c, ( c, d ) => c ).Count();
    Console.WriteLine( q);

    // シーケンスの比較
    Console.WriteLine(
      Enumerable.Range( 1, 2 ).SequenceEqual( Enumerable.Empty<int>() ) );

    Console.ReadKey();
  }
}

シーケンスの比較って、ものっすごい抽象度が高い操作ですねぇ。MoveNext() とか細かい動作から、よくもまあこの高みにまで達したなあと。

*1:この言葉は McIlroy 神が Unix のパイプ&フィルタについて語った言葉。The Art of UNIX Programming より引用。McIlroy さんはパイプ&フィルタの生みの親。

トラックバック - http://d.hatena.ne.jp/siokoshou/20071231

2007/12/30 (日)

[] 配列の共変性はちょっと壊れてる  配列の共変性はちょっと壊れてるを含むブックマーク  配列の共変性はちょっと壊れてるのブックマークコメント

元ネタ http://blogs.msdn.com/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx


配列の共変性はちょっと壊れてるというお話。試してみました。

このコードは正常に実行できるでしょうか?コンパイルエラーでしょうか?それとも例外発生でしょうか?例外だとしたらいつ発生するでしょうか? Student代入時?表示の際?答えはコードの後に。

using System;

namespace Variance
{
  class Program
  {
    static void Main()
    {
      Person[] persons = new Employee[] {
        new Employee { Name = "Aramaki", ID = 1 },
        new Employee { Name = "Ishikawa", ID = 2 },
        new Employee { Name = "Saito", ID = 3 } };

      persons[ 0 ] = new Student { Name = "Yasako", Grade = 5 };

      Array.ForEach( persons, Console.WriteLine );
      Console.ReadKey();
    }
  }

  class Person
  {
    public string Name;

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

  class Employee : Person { public int ID; }

  class Student : Person { public int Grade; }
}

正解は、実行時、Student 代入の際に例外が発生します。

Ericさんは、コンパイラがエラーを検出できないことを「壊れている」と言ってます。いい感覚ですね。

なんでそうしたのかってのは、CLRJava 互換の道を選んだからだそうです。MS は .NET で Java って道を捨ててないのかな。MS が手を出せなくてもほかの誰かが…とか考えているのかな。

ちなみに、デリゲートの共変性と反変性のほうはコンパイル時にエラーを検出してくれます。これはまたそのうち、って年越しそうw

(追記) せっかくなので、配列の共変性についてもうちょっとメモ。きっと使わない機能だし(^^;

  • 共変性は参照型の配列だけにある。値型の配列にはない。
  • 「Person[ ] persons = new Employee[ ] {...」は、EmployeeをPersonに暗黙に変換できるので、Employee[ ]をPerson[ ]に変換できる。これが共変性。
  • Person[ ]は実際にはEmployee[ ]のこともある、ということ。
  • それはつまり、Person[ ]にPerson型を入れたら実行時例外が起きる場合があるということ。
  • 暗黙の参照変換だけでなく、明示的な参照変換がある場合でもOK。当然だけど、要素の変換には明示的な変換が必要。

[] 訳語  訳語を含むブックマーク  訳語のブックマークコメント

「式ツリー」って訳がねぇ。ど〜もねぇ。ルー大柴っぽくってwww

二分木、構文木とか定着してるから式木でいいと思うんだけど。

トラックバック - http://d.hatena.ne.jp/siokoshou/20071230

2007/12/27 (木)

[] yield 再帰  yield 再帰を含むブックマーク  yield 再帰のブックマークコメント

Composit パターンを使った木構造イテレータを書こうとして、ふと yield で再帰ってどうやるんだ?とちょっと考え込んでしまったのでメモ。わかってしまえば、あーこれ再帰だよねぇと納得ですが、パッと見、再帰に見えない罠。

せっかくなのでファイル/ディレクトリ用のイテレータを書いてみました。LINQ のエサにどうぞ。

ちなみに、C#の言語仕様書にもサンプルが載ってます。あと、Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本 にもズバリそのものが出てます。ちょっと横道にそれるけど「Head Firstデザインパターン」は今年読んだ本で一番でした。今年は図書館にずいぶん行ったんで、読んだ本は多いような気がしますが(読みきれないことが多いけどw)、間違いなくこれが一番です。パターン本ですが、オブジェクト指向の原則もいろいろと出てきます。オブジェクト指向をうまく使えないって悩みをちょっぴり解決してくれたかも?というわけでこの本のいいところを列挙。

  • GoF23パターン中の特に使えるものだけを重点解説
  • 読者に考えさせるのがうまい。一方的な説明じゃないのが良い
  • 問題を提起してそれをパターンで解決してみせるので、どこでどう使うってのがよくわかる
  • 同時に、変化をどう許容するかというプログラムを書くうえで最大の問題への対処方法を示している
  • 繰り返し繰り返し疎結合にしろ、継承を使いすぎるな、継承よりコンポジションをなどと現代的なOOの原則を説いている
  • 変な本なので頭に残る(今回も、あ、これ、レストランのメニューと思ったw)。この本が変なのは頭に残るようにとの配慮から
  • パターンとパターンの対話やQ&Aが、パターン間の違いなど様々な疑問を解決してくれる
  • ふざけた本なのに奥が深い。開放閉鎖原則とか依存性反転原則とかKISSとか

その後、メイヤー本も手にとってみたけど、そっちはどうもイマイチ…。厚すぎ。

閑話休題

using System.Collections.Generic;
using System.IO;

namespace FileEnum
{
  /// <summary>FileSystemInfoの行きがけ順(preorder)列挙子</summary>
  public sealed class FileSystemInfoPreorderEnumerator : IEnumerable<FileSystemInfo>
  {
    private DirectoryInfo dir;

    public FileSystemInfoPreorderEnumerator( DirectoryInfo dir )
    {
      this.dir = dir;
    }

    public IEnumerator<FileSystemInfo> GetEnumerator()
    {
      if ( this.dir == null )
        yield break;

      foreach ( FileSystemInfo item in this.dir.GetFileSystemInfos() )
      {
        yield return item;

        DirectoryInfo subDir = item as DirectoryInfo;
        if ( subDir != null )
        {
          foreach ( FileSystemInfo subDirItem in new FileSystemInfoPreorderEnumerator( subDir ) )
          {
            yield return subDirItem;
          }
        }
      }
    }

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

  public static class FileSystemInfoEnumerator
  {
    public static IEnumerable<FileSystemInfo> Preorder( this DirectoryInfo info )
    {
      return new FileSystemInfoPreorderEnumerator( info );
    }
  }
}

気軽な遊びで書いたものなので、バグってたらすみません。

拡張メソッドはおまけです。クラス名がちょっと長いので付けてみました。行きがけ順以外のイテレータを書いたときに、拡張メソッドの名前を Inorder(), Postorder() などと追加していくとわかりやすいですね。拡張ゲッターが書ければ () が消えてもっときれいだけど、そんなこといってると際限ないですね(^^;

DirectoryInfo の GetDirectories() や GetFiles() を使えば、as を使うよりスマートに書けますが、今回は使いませんでした。

これが再帰?と思ったなら、GetEnumerator() にブレークを張って動かしてみてください。

これを使うサンプルはこちら。

using System;
using System.IO;
using System.Linq;

namespace FileEnum
{
  class Program
  {
    static void Main()
    {
      var dir = new DirectoryInfo( @"D:\VS9Projects" );
      foreach ( var item in dir.Preorder() )
      {
        if ( item is DirectoryInfo ) Console.Write( "[Dir] " );
        Console.WriteLine( item.Name );
      }

      Console.WriteLine();

      var q = from file in dir.Preorder()
              where file.Extension == ".cs"
              select file;

      foreach ( var item in q )
      {
        Console.WriteLine( item.Name );
      }

      Console.ReadKey();
    }
  }
}

「D:\VS9Projects」は私の砂場。適当に変えて実行してください。

nsharpnsharp 2007/12/27 12:50 入り口をDirectoryInfoに制限しなければ、かなり簡潔になりますね。

  static IEnumerable<FileSystemInfo> EnumFileSystemInfos(FileSystemInfo root) {
    yield return root;

    var dir = root as DirectoryInfo;
    if (dir == null) {
      yield break;
    }
    foreach (var fsi in dir.GetFileSystemInfos().SelectMany(f => EnumFileSystemInfos(f))) {
      yield return fsi;
    }
  }

  // EnumFileSystemInfos(new DirectoryInfo(@”D:¥VS9Projects”)).Take(100)

nsharpnsharp 2007/12/27 12:57 あ、Skip(1) しないと動作結果が同じになりませんね・・・。

siokoshousiokoshou 2007/12/27 17:49 なるほど!その発想は(ry
再帰的な構造ならではの書き方ですね!

nsharpnsharp 2007/12/27 18:05 やっぱり入り口がFileSystemInfoなのは違和感があるので、修正してみますた。

  static IEnumerable<FileSystemInfo> EnumFileSystemInfos(DirectoryInfo root) {
    foreach (var item in root.GetFileSystemInfos()) {
      yield return item;

      var dir = item as DirectoryInfo;
      foreach (var subitem in dir != null ? EnumFileSystemInfos(dir) : Enumerable.Empty<FileSystemInfo>()) {
        yield return subitem;
      }
    }
  }

こっちの方が短かった。(´・ω・`)

siokoshousiokoshou 2007/12/28 18:50 お〜、スマートですねぇ。なんだかしてやられた感でいっぱいですw
#これの前に書いたのがclassにIEnumerable<T>を持たせたやつだったので、引きずられて古典的なclass形式で書いてしまったorz

トラックバック - http://d.hatena.ne.jp/siokoshou/20071227

2007/12/21 (金)

[] 宣言型とか XML とかだらだらと  宣言型とか XML とかだらだらとを含むブックマーク  宣言型とか XML とかだらだらとのブックマークコメント

ふと、XmlSerializer で XML の読み書きするのって宣言型プログラミングだよね、とか書いてみたりして。XmlElement 属性を使って、XML 要素名を .NET のクラス名にマッピングしたりだとか、そういうあたりが。属性は .NET 登場時から宣言型プログラミングって触れこみだから、まあ当然というかそのままなんだけど。XmlReader やらなにやらと比べてとても楽ですね。もちろんその分できないことは多いです。public フィールドかプロパティじゃないとダメって制限がなければなぁ…。

XAML みたいに XML 要素でも XML 属性でもそんなの関係ねぇってのを実現する XmlElementOrAttribute みたいな属性(ややこしいけど XML の属性じゃなくて .NET の属性)がないかなと探してみたけど、なさげですね。XmlAnyAttributeAttribute とか使えば作れそうだけど、XElement を調べるほうが先かな。

そういえば、VS2008 についてくる sgen.exe も、/t で一つの型しか指定できませんでした(参考: id:siokoshou:20071104)。む、英語版から日本語版 Express に入れ替えたら SDK が違うフォルダに再インストールされてる…。そこに入れないで欲しかったorz

の XML をそのまま書ける機能はどうして VB に入って C# に入らなかったんだろう。ガラじゃないってのはちょっと思うけど、それを言ったらクエリ式も従来の C# と全然毛色が違うし。まあ XML love じゃないんでどっちでもいいんだけど(^^;

ufcppufcpp 2007/12/23 17:52 XMLがこの先も(10年・20年と)ずっと標準であり続けるとも限らないので、C#に組み込むのは怖かったという話だそうです。
http://www.infoq.com/news/2007/03/CSharp-XML
それと、C#チーム的にはどちらかというと、Entity FrameworkのXML版のような、オブジェクト⇔XMLマッピングフレームワークを作りたいんじゃないかと思います。

siokoshousiokoshou 2007/12/24 17:49 おぉ!まさかこの部分にコメントが付くとは思ってませんでした。裏話大好きです、ありがとうございます。じっくり読んでしまいました。さらにその記事からリンクしてあるチャットも読んでしまいました。
XMLが変わることまで心配しているってのはかなり驚きでした。確かにHTML/XHTMLに起こった事を考えると変わることもあるかもしれないですねぇ。あと、XMLうざいって意見は根強いですし。.NETってXMLべったりだと思ってたけど、一定の距離は必要と考えてる人たちもいるってことなんですね。
MSとしてはXMLリテラル使いたければVB使えと言えるし、XMLに何か起きたらC#使えって言えるので、それぞれ違うってのはうまいですね。
マッピングフレームワークですか。Entity Frameworkはまだ全然見てなかったり(^^;

トラックバック - http://d.hatena.ne.jp/siokoshou/20071221

2007/12/12 (水)

[] Select は?  Select は?を含むブックマーク  Select は?のブックマークコメント

http://msdn2.microsoft.com/en-us/library/ckzcawb8(VS.90).aspx

Select() と SelectMany() はどこ?

dotnetfandotnetfan 2007/12/12 11:22 拡張メソッドはこっちです。
http://msdn2.microsoft.com/en-us/library/system.linq.enumerable_members(VS.90).aspx

NyaRuRuNyaRuRu 2007/12/12 11:29 一部の拡張メソッドは ckzcawb8(VS.90).aspx にリストアップされているのに,なんでか Select() やSelectMany() が漏れている,というのがポイントですかね.
ここは漏れている拡張メソッドをリストアップするコードを LINQ to XML で書くべきところ?

siokoshousiokoshou 2007/12/12 20:27 おぉ、ありました!ありがとうございます。
中途半端に漏れがあるのはどうにかして欲しいですね。というか、検索の改善を…

トラックバック - http://d.hatena.ne.jp/siokoshou/20071212

2007/12/8 (土)

[] LINQ to Object のイディオム その2 : 集計演算子, 要素演算子, 限定子  LINQ to Object のイディオム その2 : 集計演算子, 要素演算子, 限定子を含むブックマーク  LINQ to Object のイディオム その2 : 集計演算子, 要素演算子, 限定子のブックマークコメント

ちょっといない隙に PLINQ/TPLVoltaInternational Pack とかw VS2008 が出たばっかりなのに、なんなんだろう(^^; 遠い未来の夢と思ってたライブラリが急に目の前にポンと出てきて、なんだか戸惑ってしまってます。で、どれで遊ぶか迷った末に決めかねて結局 LINQ to Object で地味に(?)遊んでみました。

LINQ to Object の変なコードばっかり載せてしまってるので、たまには実用的な例を一挙に。遅くなるだけ、ややこしいだけじゃない LINQ to Object の簡単で便利なコードサンプルです。このサンプルのあたりなら、誰もが今すぐに恩恵を受けられるハズ。

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

static class Program
{
  static void Main()
  {
    var dic = new Dictionary<String, int>
      { { "バナナ", 10 }, { "いちご", 398 }, { "りんご", 298 } };

    Console.WriteLine( "Keys: " + String.Join( ", ", dic.Keys.ToArray() ) );
    Console.WriteLine( "Values: " + String.Join( ", ",
      dic.Values.Select( n => n.ToString() ).ToArray() ) );

    Console.WriteLine( "Reverse: " +
      String.Join( ", ",
        ( from n in dic.Values.Reverse() select n.ToString() ).ToArray() ) );
    Console.WriteLine( "Sum: " + dic.Values.Sum() );
    Console.WriteLine( "Sum2: " + dic.Sum( e => e.Value ) );
    Console.WriteLine( "Count: " + dic.Count() );
    Console.WriteLine( "Count (n < 300): " + dic.Count( e => e.Value < 300 ) );
    Console.WriteLine( "Min: " + dic.Min( e => e.Value ) );
    Console.WriteLine( "Max: " + dic.Values.Max() );
    Console.WriteLine( "Average: " + dic.Average( e => e.Value ) );
    Console.WriteLine( "Reverse (Aggregate): " +
      dic.Keys.Reverse().Aggregate( "", ( s, n ) => s + n.ToString() + ", " ) );

    Console.WriteLine( "dic[ 2 ]: " + dic.ElementAt( 2 ) );
    Console.WriteLine( "Contains いちご: " + dic.Keys.Contains( "いちご" ) );
    Console.WriteLine( "Contains 柿: " + dic.Keys.Contains( "柿" ) );
    Console.WriteLine( "All (10 <= n): " + dic.Values.All( n => 10 <= n ) );
    Console.WriteLine( "All (n < 100): " + dic.Values.All( n => n < 100 ) );
    Console.WriteLine( "Any (n < 5): " + dic.Any( e => e.Value < 5 ) );
    Console.WriteLine( "Any (n < 100): " + dic.Values.Any( n => n < 100 ) );

    Console.WriteLine( "SingleOrDefault (n < 100): " +
      dic.SingleOrDefault( e => e.Value < 100 ) );
    Console.WriteLine( "SingleOrDefault (n == 5): " +
      dic.SingleOrDefault( e => e.Value == 5 ) );
    Console.WriteLine( "First: " + dic.First() );
    Console.WriteLine( "Last: " + dic.Last() );

    Console.ReadKey();
  }
}

実行結果

Keys: バナナ, いちご, りんご
Values: 10, 398, 298
Reverse: 298, 398, 10
Sum: 706
Sum2: 706
Count: 3
Count (n < 300): 2
Min: 10
Max: 398
Average: 235.333333333333
Reverse (Aggregate): りんご, いちご, バナナ,
dic[ 2 ]: [りんご, 298]
Contains いちご: True
Contains 柿: False
All (10 <= n): True
All (n < 100): False
Any (n < 5): False
Any (n < 100): True
SingleOrDefault (n < 100): [バナナ, 10]
SingleOrDefault (n == 5): [, 0]
First: [バナナ, 10]
Last: [りんご, 298]

いろんな書き方ができることを示すために、わざといろいろな書き方をしました。C#2.0 では、Array と List<T> だけは便利なメソッドがたくさんあるけど、Dictionary あたりになると全部自分で書かなければいけなかったのが、LINQ to Object で一気に解決してしまいました。ありがたいことです。LINQ の勉強をどこから始めようか迷ってるなら、このあたりが最も実用的と思うのでおすすめです。

参考: .NET 標準クエリ演算子

(追記)辞書のエントリ順に依存したコードは書かないようにご注意を。あくまでもサンプルですので。念のため。

トラックバック - http://d.hatena.ne.jp/siokoshou/20071208
2005 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 06 | 09 | 11 | 12 |
2007 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 08 | 09 | 10 | 12 |
2009 | 01 | 03 | 04 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 07 |
2011 | 04 | 07 | 10 |
2012 | 04 | 12 |
2013 | 08 |
2014 | 03 | 08 |
2017 | 09 |
Connection: close