はい、随分期間が空きましたが、別に勉強サボってた訳じゃ無くて、単にブログ書くのが面倒くさかっただけです。
チマチマとC#で作る様になってきてJavaと比較して使いにくい点もままあるけど(特にInterfaceがデフォルト実装持てないのと、String.Spritが使いにくい、インデント幅4は長すぎる)、まぁ、良い言語かなと思います。
文法は、JavaとC++をやっていれば、微妙な違いはありますが、だいたいどちらかの言語にある概念を持ってくれば理解できます。
というわけで、文法はすっ飛ばして、データ型とその処理について書いていこうと思う。言語を使いこなせるかどうかって、結局、基本データ型(コレクション、ファイル、時間、Thread)の操作を使いこなせるかどうかが結構大きいからね。
最初は配列からだ。
配列
配列の型宣言は型[]だけが許されるっぽい。参照型で全てArrayクラスの継承クラス。各要素の初期値は0かnullである。
int[] array = {1,2,3,4}; // int array[] = {1,2,3,4}はだめ
配列はおおよそ以下の種類がある。
- 1次元配列
- 多次元配列
- ジャグ配列
- スタック上の配列(Span)
多次元配列はJavaにはない配列で、中身は1次元配列だけど、添え字で多次元的にアクセス出来る配列。
以下の例の様に、array2dの添え字は、arrayのと等価だ。(Xは1次元の長さ) 次元はRankに格納されていて、各次元の長さはGetLength(n)から取得出来る。
int[] array = {1,2,3 , 4,5,6}; int[,] array2d = {{1,2,3}, {4,5,6}}; Console.Write( "[1,2] => array[1*3+2]({0}), array2d[1,2]=({1})", array[2 + 1*3 ], array2d[1,2]);
なお、多次元配列はforeachの前には無意味だ。
//どちらも同じ。 foreach( var i : array) Console.WriteLine(i); foreach( var i : array2d) Console.WriteLine(i);
次に、ジャグ配列はJavaの配列と同じで、配列の要素に別の配列を入れるタイプの配列だ。各要素ごとに長さが違う配列を入れることが出来る。
int[][] array2dJ = new int[2][]; array2dJ[0] = new int[5]; array2dJ[1] = new int[3];
最後に、なんとC#はnewせずに直接スタック上に配列が確保出来る。スゲぇ。Javaにもくれ。
// newではなく、スタック上に確保(解放コスト無し) Span<int> array = stackalloc int[10]; array[0] = 100;
なお、C#7.2以上じゃないと駄目なので、program.csprojのPropertyGroup.LangVersionをlatestとかにしておくこと。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.1</TargetFramework> <DebugType>Full</DebugType> <LangVersion>latest</LangVersion> </PropertyGroup> </Project>
配列の各要素を結合した文字列を作成
String.Joinで各要素を結合した文字列を作ることが出来る。
int[] array = { 1, 2, 3, 4, 5, 6 }; Console.WriteLine("array={0}",String.Join(",",array));
ただし、多次元配列だと駄目。面倒くさい。
部分配列(Span)
Spanを使うことで部分配列を取り出すことが出来る。
ただし、やっぱり多次元配列だと駄目。(DangerousCreateで出来る?)
多次元配列いらなくない?[x + y * width]って書くのJavaで慣れてしまったし、正直、デメリットの方が大きいわ。
Cの多次元配列と同じ構造なら、多次元から1次元にポインタ変換出来そうな気がするんだけど、手段無いのかな?
int[] array = { 1, 2, 3, 4, 5, 6 }; var span = array.AsSpan(2,3);// new Span<int>(array,2,3); span[0] = 100; span[1] = 101; span[2] = 102; //span[3] = 103; これはエラー // {1,2,100,101,102,6} Console.WriteLine("array={0}",String.Join(",",array));
特定のデータで埋める(JavaのArrays.fill)
JavaのArrays.fillはArray.Clear,Array.Fillそれに当たる。0やnull初期化したいならArray.Clearが引数が無くて楽かも知れない。
int[] array = { 1, 2, 3, 4, 5, 6 }; // {1,0,0,0,5,6} Array.Clear(array,1,3); // {1,0,0,100,100,6} Array.Fill(array,100,3,2);
検索や反転、コピー
バイナリサーチ、反転、コピーなどのJavaではArraysクラスにstaticメソッドで提供されている関数は、C#ではArrayに一通り揃っている。
ちなみに、int[]の配列では無く、Span
int[] array={1,2,3,4,5,6}; Span<int> span = array; Array.Reverse(array); span.Reverse();
map,filter
JavaでのmapやfilterはC#ではLINQで実現される。Javaと違って、Streamにしなくてもそのまま使える。
だが、これがぶっちゃけ言って全然慣れない!!
なんで、mapがSelectなん?SQLぽさなんていらんわ。Selectって響き的にfilterっぽいじゃん。で、filterがなんでWhereなん?英語的には条件式ってIf,Whenもありうるじゃん。Aggregate?英語音痴の私、この単語知らない。(集計)
正直この名前付けには不満しかないわ。flattenないし。何でmap→flattenを一つにしてSelectManyにしたわけ?
逆に、JavaのStreamでは削除されたzipがあるのは素晴らしい。
Java | C# |
---|---|
map | Select |
filter | Where |
reduce | Aggregate |
sort | OrderBy |
sum,max,min,distinct | Sum,Max,Min,Distinct |
first | FirstOrDefault |
なし | Zip,Concat,Join |
flatten | SelectMany(_=>_) |
toArray | ToArray |
collect(toList()) | ToList |
groupingBy | GroupBy |
parallel | AsParallel |
IntStream.range | Enumerable.Range |
Javaに無いJoinは、最初よく分からんかったけど、こういうことらしい。SQLの内部結合と同じ物を表現する為にあるらしい。SQLとか触ったことしかないからなぁ。
array.Join(array2, func1 , func2 , func3);
// ↓
array.Zip(array2,(x,y) => (x,y))
.When(x => func1(x.x) == func2(x.y))
.Select(x => func3(x.x,x.y))
以上、次はコレクションかな。