「C Sharp」の版間の差分
→条件分岐: if文 |
M →if文: 見出しレベルの修正 |
||
81 行 | 81 行 | ||
=== 条件分岐 === |
=== 条件分岐 === |
||
=== if文 === |
==== if文 ==== |
||
if文(「イフぶん」と読む)をもちいた処理により、直後の条件を満たした場合にだけブロック内の処理を実行させることができます。ここでいう「ブロック」とは、if文の直後の<code>{</code>から次の<code>}</code>までにある、ひとかたまり(英語でブロック)の処理のことです。 |
if文(「イフぶん」と読む)をもちいた処理により、直後の条件を満たした場合にだけブロック内の処理を実行させることができます。ここでいう「ブロック」とは、if文の直後の<code>{</code>から次の<code>}</code>までにある、ひとかたまり(英語でブロック)の処理のことです。 |
||
2022年6月18日 (土) 16:24時点における版
C#とは、マイクロソフトによって開発されたプログラミング言語です。主な特徴としては、Javaに類似した言語構文を採用していること、.NET Coreというマイクロソフトが策定した共通言語基盤で動作することです。
目次
※ 以下、サブページ未作成
- C Sharp/条件分岐
- C Sharp/ループや繰り返し
※編集中
C# はGUIも作成できますが、本ページでは説明の初期のうちは、コンソールアプリ(DOSプロンプトのような画面にテキスト表示するプログラムのこと)のプログラムを解説したいと思います。
C++になくてC#にある機能は多々ありますが、特にC++との違いが大きい話題を挙げたいと思います。
なお、C# は、Visual C# とは別言語です。ネット上には、Visual C# でしか通用しない話題を「C#」の名前で説明しているものもありますが、不正確な説明です。
ここでいう Visual c# とは、マイクロソフト社がWindowsアプリをC#風の言語で開発できるようにしたマイクロソフト独自言語のことです。もしかしたらマイクロソフト自身、Visual C# の意味で「C#」と説明しているかもしれませんが、単なるC#自身の国際規格には、特にwindowsアプリに特化した仕様・規程などは無いと思われます。
C# は現在では国際規格化されており、マイクロソフトの仕様とは独自に国際規格が存在しています。
書式指定
C# では、表示位置の指定には、引用符中での波カッコ { }
を使います。なお、一般に、引用符中で表示位置指定のために使われる波カッコ { }
のことを「プレースホルダー」と言います。
C++ の%d などと違い、C#では引数の順番どおりに書く必要はないです。ただし、たとえば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("{0:d} ", 13) ; // 10進数 Console.WriteLine("{0:g} ", 13) ; // general } }
- 実行結果
13 13
数値以外も対応可能な形式として、特に指定のない事を明示する場合、 {0:g}
のように「:g」をつける方法もあります。一般 general の g です。
条件分岐
if文
if文(「イフぶん」と読む)をもちいた処理により、直後の条件を満たした場合にだけブロック内の処理を実行させることができます。ここでいう「ブロック」とは、if文の直後の{
から次の}
までにある、ひとかたまり(英語でブロック)の処理のことです。
C#のif文の記法や使い方は、標準C言語やC++とほぼ同じです。
using System; public class Hello { public static void Main(string[] args) { int a =7; if (a > 5) { Console.WriteLine("5より大きい"); } if (a <= 5) { Console.WriteLine("5以下です"); } } }
- 実行結果
5より大きい
言葉で説明するのは難しいので、上記コードを手元の実行環境にコピーして、aの値を色々と変えてみて実験してみましょう。
構文としては、
if (条件) { // 条件が満たされた場合にこのブロックが実行される }
です。
if文のあとにelse if
を使うと、直前のif文が実行されなかった場合にだけ条件判定させることができます。下記のように使います。
なお、if のない単独のelse
は、単に前の条件が満たされなかっただけでもブロック内が実行されます。
using System; public class Hello { public static void Main(string[] args) { int a = +2; if (a > 5) { Console.WriteLine("5より大きい"); } else if (a >= 0 && a <= 5) { Console.WriteLine("0以上かつ5以下です"); } else { Console.WriteLine("0未満です"); } } }
- 実行結果
0以上かつ5以下です
else if で判定が行われる条件として直前のif文が満たされなかった場合でなければなりません。なので、もし直前のif文が満たされるように条件を書き換えると、else if は実行されなくなります。試してみましょう。
下記コードは、やや条件分岐のバグを含んでいます。
// 条件分岐にバグあり using System; public class Hello { public static void Main(string[] args) { int a = +2; if (a < 5) { // 不等号を逆に書き換えた Console.WriteLine("5より大きい"); } else if (a >= 0 && a <= 5) { Console.WriteLine("0以上かつ5以下です"); } else { Console.WriteLine("0未満です"); } } }
- 実行結果
5より小さい
「0以上かつ5以下」という条件は満たしているのにもかかわらず、else if の直前のif文が満たされているので、else if のブロックは実行されていません。
この例のように、else if は、なれずに使うとバグにつながります。elseの仕組みを正しく理解して、どうしても必要な場合にだけ else if や else を使いましょう。
当然ですが、直前にif がないのに else if を使うとエラーになります。また、直前が else で終わってるのに続けてelse if を使ってもエラーになります。また、if と else や else if のあいだに Console.WriteLine(a);
など別の命令があってもエラーになります。
単なるswitch文
まず、switch文やenumはC#では下記のように使う。.net core 6 で下記コードが動くので、C#10 に対応しているはず。
switch文についての説明は省略する。2022年6月現在、まだ当wikiにC#のif文やswitch文の教材は無いので、標準Cなどの解説を参照してもらいたい。
※ 下記は .net core でのプログラム
using System; week weekinst = week.Saturday; switch (weekinst) { case week.Saturday: Console.WriteLine("土曜日"); break; case week.Sunday: Console.WriteLine("日曜日"); break; default: Console.WriteLine("平日"); break; } enum week { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }
- 実行結果
土曜日
enumについては、後述する列挙型のセクションを参照せよ。
enumを使わなくてもswitch文は書けるが、現代では、なるべくenumを使って書くのが望ましい。
enum を使わない場合、たとえば下記のように、条件を意味する数値をプログラマー側で決めておいて割り当てをしなければならず、色々と問題がある(プログラマーごとの割り当てルールのバラツキの問題、追加で挿入の条件があると以降の番号を振り直しの問題、など)。
using System; public class Hello { public static void Main(string[] args) { const int Saturday = 0; const int Sunday = 1; const int Monday = 2; const int Tuesday = 3; // 長いので省略 var weekinst = Monday; // とりあえず月曜日 switch (weekinst) { case Saturday: Console.WriteLine("土曜日"); break; case Sunday: Console.WriteLine("日曜日"); break; case Monday: Console.WriteLine("月曜日"); break; default: Console.WriteLine("火曜から金曜"); break; } } }
- 実行結果
月曜日
もしenumを使わないと、たとえば「月曜日(Monday)を0にして数え始めたい」とか顧客の要望などが変わった場合、下記コードの部分の番号を振り直しになるのである。このようにenumを使わないと色々と非効率である。
const int Saturday = 0; const int Sunday = 1; const int Monday = 2; const int Tuesday = 3; // 長いので省略
switch式
C#7~8 から「switch 式」というのが追加された。下記のような処理ができる。
※ 下記は .net core でのプログラム
week weekval = week.Saturday; string message = weekval switch { // switch式用のインスタンス的なものを作成しないといけない week.Saturday => "土曜日" , week.Sunday => "日曜日" , _ => "平日" , // default に相当 }; Console.WriteLine(message); enum week { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }
- 実行結果
土曜日
switch式では条件に「default」は使えない。アンダーバー_
で指定した条件以外の値の場合の結果を指定する。
C#のコンパイラでは、switch式の全ての値を網羅してないかぎり、アンダーバー_
で指定しないと、警告が出る。よって上記のようなコードになる。
なお、
week.Saturday =>Console.WriteLine("土曜日"); // エラー
と書いても、うまく行かない。コンパイラにvoid型に変換できないと出るが、しかし
void message = weekval switch { // エラー
などと書き換えてもエラーになる。
よって、上記コードのように、switch式の結果(右側)では、命令ではなく値だけを指定して事前に用意された変数に代入することになる。別途、その結果を代入した変数をつかって目的の処理をする命令が必要である。
列挙型
条件分岐に限った機能ではないのですが、 enum というキーワードを使う列挙型(Enumeration types)という機能が標準C言語の時代からあり、C++にもC#にもenumがあります。
enum は下記のように使います。なおコードはMicrosoft Doc のものを『列挙型 (C# リファレンス)』2022/04/06、当wikiで紹介した実行環境でも動くように修正したものです。
※ 下記は .net framework および mono のコード
using System; public enum Season { Spring, Summer, Autumn, Winter } public class EnumConversionExample { public static void Main() { Season a = Season.Autumn; Console.WriteLine($"Integral value of {a} is {(int)a}"); // output: Integral value of Autumn is 2 var b = (Season)1; Console.WriteLine(b); // output: Summer var c = (Season)4; Console.WriteLine(c); // output: 4 } }
- 実行結果
Integral value of Autumn is 2 Summer Winter
まず、C# の enum は、特にクラスで包含する必要はありません。このように enum はC#では特殊なクラスのようなものとして扱われています。
enum は、定数のグループを作るためのものであり、ブロック内での定数の宣言順に 0,1,2,3,・・・の定数値を割り当てするものです。なんのためにこれをするかというと、用途は多々考えられますが、よくswitch文への応用が挙げられます。
ともかく、enum は広い意味でのクラスの一種なので、要素へのアクセス方法も、Season.Autumn
のように
enum型名.変数名
です。なお、enumの要素のことをプログラミング一般で「列挙子」とか「列挙定数」と言います。つまり、
public enum 列挙型名 { 列挙子名0, 列挙子名1, 列挙子名2, 列挙子名3 }
です。
広い意味でのクラスの一種と言いましたが、しかしenumはC#よりも昔からあることもあってか、newなどのキーワードによるインスタンスの作成は不要です。そもそも書き換えをしないので、インスタンスの作成は不要です(インスタンスは呼び出し元ごとに書き換えをしたりアレンジしたりするためのものだから)。
c# の enum の場合、var b = (Season)1;
をenum型名でキャストすることで、割り当て数値→列挙子名への逆引きができます。
ですが、列挙子に割り当てられてないものをキャストしようとした場合、そのまま数値が変えされます。割り当てられてない数値が与えられても、そこでプログラムが止まったりはしません。
配列
同じ型のデータを番号をつけて、ひとまとめにしたものを配列(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#の提供する専用機能を使うことで、コードが短くなったり、集団開発ではプログラマーたちによるバラツキが無くなるので品質が一様化して効率化したり、いろいろと便利です。また、コードの各部の意図も、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
foraech
マイクロソフトの公式サイトにもありますが、たとえば foreach という機能が、C++ ではなかなか入門レベルでは見かけず独特です。(C++にもC++11以降は似たような機能はあるが、記法がだいぶ違う。)
以下、マイクロソフトmsdnのコード例『配列での foreach の使用 (C# プログラミング ガイド)』2022/04/06(2022年6月に確認)を参考にしたものです。
using System; public class Hello { public static void Main(string[] args) { int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 }; foreach (int i in numbers) { System.Console.Write("{0} ", i); } System.Console.Write("\n"); } }
- 実行結果
4 5 6 1 2 3 -2 -1 0
foreach は、配列などのグループ的なまとまりをもったデータ構造をfor的に巡回させたいとき、いちいちループ条件文にて配列の要素数を指定しなくても、上述のようにforeach文中で in 配列名
を指定すれば、それをもとに自動的に第0項から最後の項まで巡回するようにループ条件を設定してくれる機能です。
正確な言い回しなどは、MSDN 公式サイトを見てください。
なお、foreach を使わずとも、C言語的な従来の記法でもfor文による繰り返し処理は可能です。
using System; public class sample { public static void Main(string[] args) { for (int i = 0; i <= 3; i=i+1){ Console.WriteLine(i); } } }
- 実行結果
0 1 2 3
なお、i=i+1
の部分はi++
と省略することも可能です(インクリメント)。
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 はそのままです。