C Sharp
本書は、マイクロソフト社が開発したプログラミング言語「C#」について解説しています。 C#は、先行するC、C++、Javaと同じファミリーに属するプログラミング言語で、マイクロソフト社の社員が.NET Foundationを通じて開発し、MITライセンスの下でリリースされている共通言語基盤 (Common Language Infrastructure; CLI)で動作することが特徴です。
C#は、多くの開発者によって広く使用されており、プログラミングの初学者から熟練者まで幅広い層に向けて設計されています。また、C#は.NET Frameworkや.NET Coreといったフレームワークを使用して開発することができ、WindowsアプリケーションやWebアプリケーションなどの開発に適しています。
目次[編集]
※編集中[編集]
C# はGUIも作成できますが、本ページでは説明の初期のうちは、コンソールアプリ(コマンド プロンプトのような画面にテキスト表示するプログラムのこと)のプログラムを解説したいと思います。
C++になくてC#にある機能は多々ありますが、特にC++との違いが大きい話題を挙げたいと思います。
C# の標準規格 |
プログラミング言語C#は、2000年にMicrosoft社のAnders Hejlsberg氏によって設計され、その後2002年にEcma(ECMA-334)、2003年にISO/IEC(ISO/IEC 23270)によって国際標準として承認されました。
2022年6月時点のC#標準
C#には以下のような独立した実装があります。
Microsoft以外の実装は、ECMA-334に基づいています。 ECMA-334の2022年6月時点での最新は VC#6.0 相当のECMA-334:2022なので、MicrosoftのVC#の比較的新しいバージョンでコンパイル出来るソースコードが、MonoなどMicrosoft以外の実装でコンパイル出来ない場合があります。 |
文字表示[編集]
文字列中での変数の表示[編集]
C# では、文字列中で変数を表示したい場合、下記コードのように表示位置の指定に引用符中で波カッコ { }
を使います。なお、一般に、引用符中で表示位置指定のために使われる波カッコ { }
のことを「プレースホルダー」と言います。
コードがないと分かりづらいので、下記コードを読んでください。
using System; public class Hello { public static void Main(string[] args) { int a = 14; Console.WriteLine("トムは{0}歳です", a); } }
- 実行結果
トムは14歳です
構文としては
Console.WriteLine("文字列の前半{0}文字列の後半", 挿入したい変数) ;
です。
挿入したい変数が複数ある場合は、
Console.WriteLine("文字列 {0} 文字列 {1} 文字列 {2}", 変数0, 変数1 , 変数2) ;
のようになります。
$ 文字列補間[編集]
特殊文字 $
は、文字列リテラルを補間文字列として識別します。補間文字列は、補間式を含む可能性のある文字列リテラルです[1]。
補間文字列が結果文字列に解決されるとき、補間式を含む項目は式結果の文字列表現に置き換えられます。
stringクラスのFormat静的メソッド の場合、プレスホルダーの数と引数の数(あるいは型)に不整合があった場合、コンパイル時にはエラーどころか警告もなく、実行時エラーとなる欠点がありますが、$ 文字列補間 は引数の数と型を間違えようがないのが大きな利点で、そもそも string.format() よりも可読性が極めて高いので使わない理由はありません。
- コード例
using System; public class Hello { public static void Main(string[] args) { Console.WriteLine($"3 + 5 = {3 + 5}"); // 十進数の11を変換するコード Console.WriteLine($"{11:x} "); // 16進数に変換 Console.WriteLine($"{Convert.ToString(11, 2)} ") ; // 2進数に変換 } }
- 実行結果
3 + 5 = 8 b 1011
- 特に書式を宣言しない場合、数値は10進数として扱われます。
- が、10進数(decimal)の出力を明示したい場合、
{11:D}
のように「:D」をつけることで可能です。
- 整数は自動的に昇格し浮動小数点として扱われる
using System; public class Hello { public static void Main(string[] args) { Console.WriteLine($"0x{Int64.MaxValue:x}"); // Hex. Console.WriteLine($"{Int64.MaxValue:g}"); // general Console.WriteLine($"{Int64.MaxValue:e}"); // exp. Console.WriteLine($"{Int64.MaxValue:f}"); // float // Console.WriteLine($"{1.2:X}"); // コレはエラーになる Console.WriteLine($"{0b1101}"); // binary literal // Console.WriteLine($"{13:b}"); // これはエラーになる } }
- 実行結果
0x7fffffffffffffff 9223372036854775807 9.223372e+018 9223372036854775807.00 13
- 整数に書式化文字列の e や f を適用すると、浮動小数型に変換され表示されます。変態ですね。
- この仕様は、型を間違えて書式化文字列を与えたことに気づく機会を奪うので注意が必要です。
- なにもチェックしていないのかというと、$"{1.2:X}" はシッカリとエラーにします。一貫性がありませんね。
- 一貫性と言えば、C#7.2 から 0b1101 のような2進数リテラルに対応したのですが、書式化文字列は2進数に対応していません。一貫性がありませんね。
@ 逐語的文字列リテラル[編集]
特殊文字 @
は、文字列リテラルを逐語的文字列リテラルとして識別します。
逐語的文字列リテラルでは、単純なエスケープシーケンス(バックスラッシュを表す「 \ 」など)、16 進エスケープシーケンス(大文字の A を表す「 \x0041 」など)、および Unicode エスケープシーケンス (大文字の A を表す「 \u0041 」など) はそのまま解釈されます。引用符のエスケープシーケンス("")のみ文字通りに解釈されず、二重引用符が1つ生成されます[2]。
- コード例
using System; public class Hello { public static void Main(string[] args) { Console.WriteLine(@" \ -- "" -- C:\temp\test.txt \x0041 \u0041 \n \t "); } }
- 実行結果
\ -- " -- C:\temp\test.txt \x0041 \u0041 \n \t
書式指定[編集]
【旧式】 — $ 文字列補間 を使いましょう。
C# の stringクラスのFormat静的メソッド では、表示位置の指定には、引用符中での波カッコ { }
を使います
C/C++ の%d などと違い、C#では引数の順番どおりに書く必要はありません[3]。ただし、たとえば3番目の引数なら {2}
のように、波カッコ内の数値で指定する必要があります。
C# の数値の16進数(hexadecimal)出力の書式指定は下記のようにセミコロン:
を使います。
ただし、2進数や8進数はこの方法には対応しておらず、その場合は Convert.ToString()
関数を使います。
using System; public class Hello { public static void Main(string[] args) { Console.WriteLine("{0} + {2} = {1}", 3, 3 + 5, 5); // 十進数の11を変換するコード Console.WriteLine("{0:x} ", 11); // 16進数に変換 // Console.WriteLine("{0:b} ", 11) ; // エラーになる Console.WriteLine(Convert.ToString(11, 2)); // 2進数に変換 } }
- 実行結果
3 + 5 = 8 b 1011
特に書式を宣言しない場合、数値は10進数として扱われます。
ですが、10進数(decimal)の出力を明示したい場合、 {0:D}
のように「:D」をつけることで可能です。
using System; public class Hello { public static void Main(string[] args) { Console.WriteLine($"{13:d} "); // 10進数 Console.WriteLine($"{13:g} "); // general } }
- 実行結果
13 13
数値以外も対応可能な形式として、特に指定のない事を明示する場合、 {0:g}
のように「:g」をつける方法もあります。一般 general の g です。
配列[編集]
同じ型のデータを番号をつけて、ひとまとめにしたものを配列(array)と言います。C# における配列は、下記のように、インスタンス的に new キーワードを用いて宣言しなければなりません。
文字列の配列の例
using System; public class sample { public static void Main(string[] args) { string[] a = new string[3] { "Alice", "Bob", "Clare" }; Console.WriteLine(a[0]); Console.WriteLine(a[1]); Console.WriteLine(a[2]); }
- 実行結果
Alice Bob Clare
数値の配列の例
using System; public class sample { public static void Main(string[] args) { int[] a = new int[3] { 5, 12, 4 }; Console.WriteLine(a[0]); Console.WriteLine(a[1]); Console.WriteLine(a[2]); } }
- 実行結果
5 12 4
配列では、同じ型どうしのものしかセットにできませんが、代わりに添字を指定することで要素に簡単にアクセスできます。
なお、配列の添字は上記のように0番から始まります。
C#に限らずプログラミング用語では配列の添字のことをインデックス( index )と言います。 インデックスは、索引、見出し、添字、指数などの意味を持つ英単語です。
宣言時の new キーワードは無くてもコンパイルでき正常に動作します。
using System; public class sample { public static void Main(string[] args) { string[] a = { "Alice", "Bob", "Clare" }; Console.WriteLine(a[0]); Console.WriteLine(a[1]); Console.WriteLine(a[2]); } }
- 実行結果
Alice Bob Clare
C#では、下記コードのように Length
プロパティで配列の要素数を取得できます。
using System; public class sample { public static void Main(string[] args) { string[] a = { "Alice", "Bob", "Clare" }; Console.WriteLine(a.Length); } }
- 実行結果
3
何かのプロパティを使う際、使われる対象のことをオブジェクトと言います。上記コードでは、a.Length
の「 a 」の部分がオブジェクトです。
あるオブジェクトをプロパティ的に処理した情報・結果にアクセスする場合、
オブジェクト . プロパティ
という記法(ドット記法)でアクセスします。
複合的なデータ構造[編集]
C#には、プログラミングを効率化するために、あらかじめ、よく使いそうなデータ構造を取り扱う仕組むが言語の機能として提供されています。
標準C言語やC++にない、よく使われる機能が、言語仕様として提供されています。
それを使わずとも、C言語などにある機能だけでもプログラミングは可能ですが、しかしC#の提供する専用機能を使うことで、コードが短くなったり、集団開発ではプログラマーたちによるバラツキが無くなるので品質が一様化して効率化が期待できます。また、コードの各部の意図も、C#の専用機能をつかうことで明確化するので効率化することが期待できます。
タプル[編集]
複数の変数を宣言する際、タプル taple と言うものを使って、( )
で、まとめて定義できます。
配列やクラスなどとの違いは、単に配列名やクラス名などに相当する名前が無くても、宣言できことですが、匿名配列や匿名クラスの存在から、「タプルには名前をつけることが出来ない」と一般化できます。
using System; public class sample { public static void Main(string[] args) { (int a, int b) = (15, 2); Console.WriteLine(a); Console.WriteLine(b); b = 5; Console.WriteLine(a); Console.WriteLine(b); } }
- 実行結果
15 2 15 5
タプルは単に変数の宣言をひとまとめに行うだけのものなので、宣言したあとは、それぞれの変数は、普通の変数と同様に上書きや計算ができます。
このため、タプルを使わなくても、ひとつひとつ変数を宣言しても、同じ結果の処理が同じくらいのソースコード量で可能です(可読性は、別の問題です)。
データ型の異なる要素どうしも、下記のようにタプルでひとまとめに出来ます。実質的に、タプルは無名クラスのとして機能します。
using System; public class sample { public static void Main(string[] args) { (int a, string g) = (15, "hello"); Console.WriteLine(a); Console.WriteLine(g); } }
- 実行結果
15 hello
タプルは無名で使うためのものですが、名前をつけても構いません。タプルに名前をつけた場合、個々の項目の要素へのアクセスには、下記のように Item1
, Item2
などのプロパティを使います。
using System; public class sample { public static void Main(string[] args) { (int a, string g) = (15, "hello"); var k = (a, g); // 変数名は k Console.WriteLine(k.Item1); Console.WriteLine(k.Item2); } }
- 実行結果
15 hello
タプルの項目に名前をつけることも出来ます。こうすれば、Itemで何番目だったかを覚える必要はないし、もしタプル内の順番の入れ替えをしてもコードの修正を減らせます。
using System; public class sample { public static void Main(string[] args) { var k = (year: 18, msg: "good morning"); Console.WriteLine(k.year); Console.WriteLine(k.msg); } }
- 実行結果
18 good morning
record型[編集]
C#9からrecord型が追加された。書き換えを禁じるデータ構造です。
2022年6月現在、マイクロソフト Docs 公式サイトにあるコードでは、なぜかエラーになる『C# 9.0 の新機能 - C# ガイド | Microsoft Docs』。下記のように修正すると、なぜか成功します。
Person person = new("Nancy", "Davolio"); Console.WriteLine(person); record Person(string FirstName, string LastName);
- 実行結果
Person { FirstName = Nancy, LastName = Davolio }
「Person」がいきなり定義されているのが妙に感じるだろうが、しかしrecordの行をpersonの行より前に移動するとエラーになります。
なお、「Person」は別にキーワードではないので、たとえば「Hito」(人)とかに変えてもコンパイル成功します。
念のため実行してみれば、
※ .net core 用のコード
Hito hito = new("Tom", "Davolio"); Console.WriteLine(hito); record Hito(string FirstName, string LastName);
- 実行結果
Hito { FirstName = Tom, LastName = Davolio }
となります。
WindowsでもLinuxでも同様の結果になります。
なお、確認に用いた .net sdk のバージョンはWindowsでは 6.0.301 であり、Fedora では 6.0.105 です。
レコードの各要素にアクセスするにはレコード名.要素名
でアクセスできます。しかし書き換えは出来ません。書き換えさえしなければ、閲覧などは出来ます。
※ .net core 用のコード
Hito hito = new("Tom", "Davolio"); Console.WriteLine(hito); // hito.FirstName = "John"; // レコードを書き換えようとするとエラーになります。 Console.WriteLine(hito.FirstName ); record Hito(string FirstName, string LastName); // Console.WriteLine(hito.LastName ); // ここに書くとエラーになる
- 実行結果
Hito { FirstName = Tom, LastName = Davolio } Tom
record型の実体は書き換えできませんが、現物を残したうえで、コピーをして部分的に書き換えることはwith
を使うことで可能です。
※ .net core 用のコード
Person person01 = new ("Nancy", "Davolio"); Console.WriteLine(person01); var person02 = person01 with{FirstName = "John"}; Console.WriteLine(person02); Console.WriteLine(person01); record Person(string FirstName, string LastName);
- 実行結果
Person { FirstName = Nancy, LastName = Davolio } Person { FirstName = John, LastName = Davolio } Person { FirstName = Nancy, LastName = Davolio }
実行結果の2行目と3行目を見比べれば分かるように、2行目の結果でwith書き換えをした派生変数 person02 を作成しても、もとのrecord型変数 person01 はそのままです。
record型は初期化が必須です。
このため、record型の変数の配列は複合リテラルで初期化される事になります。
『C# 9.0 の新機能 - C# ガイド | Microsoft Docs』を読みにいっても、record型の変数をどう配列化するかとか、そもそも配列化できるのか、配列化を想定しているかすら、まったく語られていません。
このように現状、record型の配列はサポートが無い状態です。
よって、どうしてもC#で配列化するような大量の記録をあつかう場合には、レコード型ではなく構造体やクラスによるインスタンスを配列化したものを使うのが良いでしょう。
- ^ $ - string interpolation (C# reference)
- ^ @
- ^ C/C++の書式指定子も "%2$d" と書くことで2番目のパラメーターを10進数で表示するなど、パラメーターの選択を書式指定子で行えます。