まずは一発目 LINQってなあに、という所で、これは以下のような事ができます。 このコードは配列から4以下の値を取り出します。 int [] data = new int[] { 3, 1, 4, 1, 5, 9, 2, 6 }; IEnumerable<int> x = from s in data where s <= 4 select s; foreach (int ite in x) System.Console.Write( "{0}," , ite); 3,1,4,1,2, これを、ちょっと短くしてみる。 int [] data = new int[] { 3, 1, 4, 1, 5, 9, 2, 6 }; foreach (int ite in from s in data where s <= 4 select s) System.Console.Write( "{0}," , ite); とてもすっきりかけます。
static T Multiply<T>(T left, T right) { var r = Expression.Parameter(typeof(T), "left"); var l = Expression.Parameter(typeof(T), "right"); return Expression.Lambda<Func<T, T, T>>(Expression.Multiply(r, l), l, r).Compile()(left, right); } Expression Treeで遊んでみた
DBを使わなくてもLINQは便利なんだよ〜!ってことを布教するためw、 普通のコレクションに対してロジックを実行するクエリのサンプルを示してみます。 >>10が言うように、グループ化の処理をクエリ一発で書けるのは実に魅力的です。 ファイル名を格納したstring配列 files に対して処理を行います。 // 拡張子ごとにファイル名をグループ化するクエリ var extGroupQuery = from filename in files let ext = System.IO.Path.GetExtension(filename).ToLower() group filename by ext into extGroup select extGroup; // グループ化したクエリは2重のIEnumerable<T>になってます foreach (var extGroup in extGroupQuery) { Console.WriteLine("Extension: {0}, Count: {1}", extGroup.Key, extGroup.Count()); foreach (var filename in extGroup) { Console.WriteLine("\t{0}", filename); } }
36 :
さらに集計処理を行います クエリの中でオブジェクトを生成し、集計処理に利用したりできます // 拡張子ごとの合計サイズを取得する(foreachを使うとこんな感じ) Dictionary<string, long> sizeDic = new Dictionary<string, long>(); foreach (var extGroup in extGroupQuery) { var sizeQuery1 = from filename in extGroup let fileinfo = new System.IO.FileInfo(filename) select fileinfo.Length; long totalSize = sizeQuery1.Sum(); sizeDic[extGroup.Key] = totalSize; } // 拡張子ごとの合計サイズを取得する(サブクエリを使う) // クエリ内でさらにクエリを定義できます(totalSizeQueryはsizeQuery1とまったく同じです) var sizeQuery2 = from extGroup in extGroupQuery let totalSizeQuery = (from filename in extGroup let fileinfo = new System.IO.FileInfo(filename) select fileinfo.Length) select new { Extension = extGroup.Key, TotalSize = totalSizeQuery.Sum() };
37 :
// .Sum()の場所を変えてみる(結果はsizeQuery2と同じです) var sizeQuery3 = from extGroup in extGroupQuery let totalSize = (from filename in extGroup let fileinfo = new System.IO.FileInfo(filename) select fileinfo.Length).Sum() select new { Extension = extGroup.Key, TotalSize = totalSize }; // サブクエリを拡張メソッドに変えてみる(場合によってはサブクエリよりもすっきり書けます) var sizeQuery4 = from extGroup in extGroupQuery let totalSize = extGroup.Select<string, long>(filename => new System.IO.FileInfo(filename).Length).Sum() select new { Extension = extGroup.Key, TotalSize = totalSize }; // extGroupQuery, sizeQuery2をまとめてみる var sizeQuery5 = from filename in files let ext = System.IO.Path.GetExtension(filename).ToLower() group filename by ext into extGroup let totalSizeQuery = (from filename in extGroup let fileinfo = new System.IO.FileInfo(filename) select fileinfo.Length) select new { Extension = extGroup.Key, TotalSize = totalSizeQuery }; とりあえず以上です。 もっと気の利いた例を作ったら、また書き込みます。
38 :
>>35-37の応用で、ファイルの重複を検出するコードをLINQでコンパクトに書いてみます。 次のメソッドが存在するという前提です。 static string GetMD5String(string filename) { using (System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider()) { byte[] data = System.IO.File.ReadAllBytes(filename); byte[] hash = md5.ComputeHash(data); StringBuilder buf = new StringBuilder(); foreach (byte b in hash) { buf.AppendFormat("{0:x2}", b); } return buf.ToString(); } }
39 :
まずは単純だけど遅いバージョンから // ファイルをハッシュ値でグループ化する // 全ファイルを読み込んでハッシュ値を計算するので遅いです var hashGroupQuery = from filehash in (from filename in files let hash = GetMD5String(filename) select new { Filename = filename, Hash = hash }) group filehash by filehash.Hash into hashGroup where hashGroup.Count() >= 2 select hashGroup; foreach (var hashGroup in hashGroupQuery) { Console.WriteLine("Hash: {0}, Count: {1}", hashGroup.Key, hashGroup.Count()); foreach (var filehash in hashGroup) { Console.WriteLine("\t{0}", filehash.Filename); } }
40 :
次に前処理を行って高速化したバージョン // 処理を高速化するため、ファイルサイズでグループ化しておく // (ファイルサイズはファイルを読み込まなくても取得できるので) var sizeGroupQuery = from filesize in (from filename in files let fileinfo = new System.IO.FileInfo(filename) let size = fileinfo.Length select new { Filename = filename, Size = size }) group filesize by filesize.Size into sizeGroup where sizeGroup.Count() >= 2 select sizeGroup; foreach (var sizeGroup in sizeGroupQuery) { // ハッシュ値でグループ化する var hashGroupQuery2 = from filehash in (from filesize in sizeGroup let hash = GetMD5String(filesize.Filename) select new { Filename = filesize.Filename, Hash = hash }) group filehash by filehash.Hash into hashGroup where hashGroup.Count() >= 2 select hashGroup; foreach (var hashGroup in hashGroupQuery2) { // (行数オーバーのため略 >>39と同じです) } }
41 :
>>35 おお >>1 です、いらっしゃいまし、どんどん布教してあげてください。 let とか小技聞いていて面白いですね。 私の方は、まだ初めて一週間と初心者なので、もっと基本的でつまらない物やっていきます。 今日は、下手をすれば、C#さえ初心者の人向けのQ&Aを作ってみようと思います。 結果の簡単な確認方法と、選択・ソートです。 明日以後、表の結合等をやってみます、そのあとは、ググればありそうなのに無いLINQの文法の詳細解説あたりをやってみます。
42 :
Q.データを確認したい ツールバーにある DataGridView は、とりあえずデータの中身を見てみたい時にとても便利です。 DataSource プロパティーにデータをセットする事で見ることができます。 ただし、ここに設定できるオブジェクトは IList , IListSource , IBindingList , IBindingListView の四つです。 また、要素は、フィールドメンバでは駄目で、プロパティーでなければなりません。 C# 3.0 より追加された省略形が便利です。 class Row { public int Data1 { set ; get ; } public int Data2 { set ; get ; } } dataGridView1.DataSource = new Row[] { new Row() { Data1 = 1, Data2 = 2 } };
43 :
Q.from ... として作ったデータが見れない IList 等に変換してしまうのが便利です。
var tableX = new [] { new { X=1 , P=0.25 }, new { X=2 , P=0.50 }, new { X=3 , P=0.25 }, }; var table2 = from row in tableX orderby row.P select row; dataGridView1.DataSource = table2.ToArray();
44 :
Q.表の中から必要な要素を選択したい where句を使います。 class Row { public int X { set ; get ; } public double P { set ; get ; } } var tableX = new Row[] { new Row() { X=1 , P=0.25 }, new Row() { X=2 , P=0.50 }, new Row() { X=3 , P=0.25 }, }; // Xが2以下の行を選択する var table1 = from row in tableX where row.X <= 2 select row; // Xが2以下の行を選択したのち、さらに P <= 0.25 となる行を絞り込む var table2 = from row in tableX where row.X <= 2 where row.P <= 0.25 select row; dataGridView1.DataSource = table1.ToArray(); dataGridView2.DataSource = table2.ToArray();
45 :
Q.表をソートしたい 1.orderby句を使います、単純なソートは以下の通りです。 class Row { public string S { set; get; } public int D { set; get; } } var tableOrg = new Row[] { new Row() { D=3 }, new Row() { D=1 }, new Row() { D=4 }, new Row() { D=1 }, new Row() { D=5 }, new Row() { D=9 }, }; // 昇順(ascending省略可) var table1 = from row in tableOrg orderby row.D ascending select row; // 降順 var table2 = from row in tableOrg orderby row.D descending select row; dataGridView1.DataSource = table1.ToArray(); dataGridView2.DataSource = table2.ToArray();
46 :
2.二つ以上の項目についてソートする場合は以下の通りです。 class Row { public string S { set; get; } public int D { set; get; } } var tableOrg = new Row[] { new Row() { S="A" , D=3 }, new Row() { S="B" , D=1 }, new Row() { S="A" , D=4 }, new Row() { S="B" , D=1 }, new Row() { S="A" , D=5 }, new Row() { S="B" , D=9 }, }; // SとD を使って比較します var table1 = from row in tableOrg orderby row.S, row.D select row; dataGridView1.DataSource = table1.ToArray();
47 :
3.二回に分けてソートする場合は以下の通りです。(2と結果が異なります) class Row { public string S { set; get; } public int D { set; get; } } var tableOrg = new Row[] { new Row() { S="A" , D=3 }, new Row() { S="B" , D=1 }, new Row() { S="A" , D=4 }, new Row() { S="B" , D=1 }, new Row() { S="A" , D=5 }, new Row() { S="B" , D=9 }, }; // SとD を使って比較します var table1 = from row in tableOrg orderby row.S orderby row.D select row; dataGridView1.DataSource = table1.ToArray();
class Row1 { public string S { set; get; } } class Row2 { public string S { set; get; } } class Row3 { public string R1 { set; get; } public string R2 { set; get; } }
var tableOrg1 = new Row1[] { new Row1() { S="赤" }, new Row1() { S="青" },}; var tableOrg2 = new Row2[] { new Row2() { S="青" }, new Row2() { S="緑" },}; var table1 = from row1 in tableOrg1 from row2 in tableOrg2 select new Row3() { R1 = row1.S, R2 = row2.S }; dataGridView1.DataSource = table1.ToArray(); dataGridView2.DataSource = table2.ToArray(); table1 の結果 赤 青 赤 緑 青 青 青 緑 この機能、一見は使い道がなさそうですが、これをフィルターしてゆく事により価値が出てきます。
52 :
Q.表を結合したい2(等価結合、きっと良く使うに違いない) Row1の表にRow2の表の要素を普通に結合します。 この例では、 tableOrg1 の Index 番号と同じ Index のある tableOrg2 の列に結合します。 class Row1 { public int LineNo { set; get; } public int Index { set; get; } } class Row2 { public int Index { set; get; } public string Data1 { set; get; } public string Data2 { set; get; } } class Row3 { public int LineNo { set; get; } public string Data1 { set; get; } public string Data2 { set; get; } }
var tableOrg1 = new Row1[] { new Row1() { LineNo=1 , Index = 3 }, new Row1() { LineNo=2 , Index = 1 }, new Row1() { LineNo=3 , Index = 4 }, new Row1() { LineNo=4 , Index = 1 }, new Row1() { LineNo=5 , Index = 5 }, }; var tableOrg2 = new Row2[] { new Row2() { Index=1 , Data1 = "Index1" , Data2="ABC" }, new Row2() { Index=2 , Data1 = "Index2" , Data2="DEF" }, new Row2() { Index=3 , Data1 = "Index3" , Data2="GHI" }, new Row2() { Index=4 , Data1 = "Index4" , Data2="JKL" }, new Row2() { Index=5 , Data1 = "Index5" , Data2="MNL" }, }; var table1 = from row1 in tableOrg1 join row2 in tableOrg2 on row1.Index equals row2.Index select new Row3() { LineNo = row1.LineNo, Data1 = row2.Data1, Data2 = row2.Data2 }; table1 の結果 1 Index3 GHI 2 Index1 ABC 3 Index4 JKL 4 Index1 ABC 5 Index5 MNL
53 :
Q.表を結合したい3(非等価結合、やや使う違いない) let を使って、選択した行を保持する事により実現できます。 class 得点表 { public string 氏名 { set; get; } public int 得点 { set; get; } } class 評価表 { public int 以上 { set; get; } public int 未満 { set; get; } public string 評価 { set; get; } } class 氏名と評価 { public string 氏名 { set; get; } public string 評価 { set; get; } }
var tableOrg1 = new 得点表[] { new 得点表() { 氏名="Aさん" , 得点=10 }, new 得点表() { 氏名="Bさん" , 得点=100 }, new 得点表() { 氏名="Cさん" , 得点=60 }, }; var tableOrg2 = new 評価表[] { new 評価表() { 以上=0 , 未満=20, 評価="丙" }, new 評価表() { 以上=20 , 未満=70, 評価="乙" }, new 評価表() { 以上=70 , 未満=101, 評価="甲" }, }; var table1 = from row1 in tableOrg1 let selectLines = from row2 in tableOrg2 where (row2.以上 <= row1.得点 && row1.得点 < row2.未満) select row2 select new 氏名と評価() { 氏名 = row1.氏名, 評価 = selectLines.First().評価 }; table1 の結果 Aさん 丙 Bさん 甲 Cさん 乙 あんま綺麗じゃない……equalsに対してLINQの拡張求む > Microsoft
>>63-66 いずれ統一化できる下準備になるのではないかと思っています。 最終的には、コンパイル済みの.NETのコードを直接 DB に送り込み、DBがそれを直接解釈するという形になるのではと想像しています。 そうしますと、C#/VB.NETで書かれたSQLとDBのSQLの微妙な解釈の違いが完全になくなると思うので、これは安心できると思うのですがどうでしょう? あと、コンパイル済バイトコードを送りつけるなら、DBのスピードもあがるかもとかとか・・・ また、セキュリティーの関係ではSQLインジェクションの類は完封できそうです。 >>1 です、今日は、表の分割、行の分類のやり方を紹介します。
68 :
Q.表を分割したい LINQの文法は、例えば以下のような形式になっています。 from アイテム名 in 元データ where アイテム名.項目=="条件のあっているデータ" select 新しい行の定義 上記の文の最後の部分「select 新しい行の定義」の部分を「group 新しい行の定義 by ふるい分けの基準となる値」 とする事により表を分割できます。
ツールボックスから DataGridView と TextBox を三つ貼り付けて以下のコードを実行すると三つの表が完成します。 var tableOrg1 = new[] { new { LineNo = 1 , Data = "ABC" } , new { LineNo = 2 , Data = "DEF" } , new { LineNo = 3 , Data = "ABC" } , new { LineNo = 4 , Data = "DEF" } , new { LineNo = 5 , Data = "GHI" } , }; var dataGridViewArray = new[] { dataGridView1, dataGridView2, dataGridView3 }; var textBoxArray = new[] { textBox1, textBox2, textBox3 }; var tableArray = (from row in tableOrg1 group row by row.Data).ToArray(); for (int i = 0; i < dataGridViewArray.GetLength(0) && i < tableArray.GetLength(0); ++i) { dataGridViewArray[i].DataSource = tableArray[i].ToArray(); textBoxArray[i].Text = tableArray[i].Key.ToString(); } by row.Data の部分を変更することにより、例えばby row.Data.Length などとする事により複雑なふるい分けが可能です。
70 :
Q.特定項目で分類したい 項目に大小比較ができれば、ソートしてしまえば良いのですが、それが無い場合には少し困った事になります。 ここでは、前出の「表を分割したい」と組み合わせた方法を紹介します。 それは、前出のLINQ文「from row in tableOrg1 group row by row.Data」の後ろに「into tmp1 from tmp2 in tmp1 select tmp2」を追加して from row in tableOrg1 group row by row.Data into tmp1 from tmp2 in tmp1 select tmp2 とすると実現できます、まじないの部類ですが・・・うまく行きます。 var tableOrg1 = new[] { new { LineNo = 1 , Data = "ABC" } , new { LineNo = 2 , Data = "DEF" } , new { LineNo = 3 , Data = "ABC" } , new { LineNo = 4 , Data = "DEF" } , new { LineNo = 5 , Data = "GHI" } , }; var table1 = from row in tableOrg1 group row by row.Data into tmp1 from tmp2 in tmp1 select tmp2; dataGridView1.DataSource = table1.ToArray();
>>73 LINQ って言葉の指す範囲は広くて、クエリ式もメソッド形式もどっちも LINQ よ。 from x in list みたいなのはクエリ式。 クエリ式でないと書きづらいのは、多重 from と join と let くらいかな。 まあ、transparent な変数が絡むとクエリ式使わないときつい。 逆に、クエリ式で書けないことも実に多い。 まあ、個人的には、 クエリ式で書けるものは全部クエリ式で、 それ以外だけメソッド形式で書いてる。
76 :
>>74 俺は 基本的に、select と group by は最後に来る。 その後ろにさらにクエリを続けたい場合は into x を置いてからさらに続ける。 と説明してる。
77 :
対応関係としては、 from x in list where x > 0 select x; ↓ list.Where(x => x > 0); // 最後の Select はきえる from x in list select x * x; ↓ list.Select(x => x * x); from x in list select x * x into y where y > 5 select y; ↓ list.Select(x => x * x).Where(y => y > 5); // .Select の後ろにさらにメソッドが続く
>>1です、一週間では見えてくるものが少ないですね、暫く使い込んでみることにしました。 Linq to SQL , Linq to XML , 生Linq と全部使い込みをしないと見えてきにくいですね。 >>78 おれ社員じゃないよ、ボーランド大好きヘジたん信者の可能性は高いけど(笑 それと食わず嫌いはどうかなwwww 問題もそれなりに多そうだなというのも見えてきましたけど開発効率めちゃくちゃ高いよぉ。