「C Sharp」の版間の差分
→※編集中: C# では、変数の型は、宣言後は(型を)変更できません。他のプログラミング言語だと、宣言時に数値を代入した変数にあとで文字列を代入できしまうのもあるが、C#ではそういうのは不可能です。 |
→※編集中: なお、 int とは、「整数」を意味する英語 integer インテジャーの略です。 |
||
14 行 | 14 行 | ||
=== 変数の宣言 === |
=== 変数の宣言 === |
||
==== var ==== |
==== var ==== |
||
変数の宣言の際、intやcharなどの具体的な型を指定しなくても、宣言時の代入で具体的な数値または文字列の代入といっしょにvar とだけ指定すれば、代入されるデータから型を自動的に判断してくれます。 |
変数の宣言の際、intやcharなどの具体的な型を指定しなくても、宣言時の代入で具体的な数値または文字列の代入といっしょにvar とだけ指定すれば、代入されるデータから型を自動的に判断してくれます。なおvarとは「変数」を意味する英語 variable (ヴァリアブル)の略です。 |
||
:<syntaxhighlight lang="csharp"> |
:<syntaxhighlight lang="csharp"> |
||
106 行 | 106 行 | ||
また、この例から分かるように int は System.Int32 と同じです。 |
また、この例から分かるように int は System.Int32 と同じです。 |
||
なお、 int とは、「整数」を意味する英語 integer インテジャーの略です。 |
|||
C# では、変数の型は、宣言後は(型を)変更できません。他のプログラミング言語だと、宣言時に数値を代入した変数にあとで文字列を代入できしまうのもあるが、C#ではそういうのは不可能です。 |
C# では、変数の型は、宣言後は(型を)変更できません。他のプログラミング言語だと、宣言時に数値を代入した変数にあとで文字列を代入できしまうのもあるが、C#ではそういうのは不可能です。 |
2022年6月18日 (土) 11:16時点における版
C#とは、マイクロソフトによって開発されたプログラミング言語です。主な特徴としては、Javaに類似した言語構文を採用していること、.NET Coreというマイクロソフトが策定した共通言語基盤で動作することです。
目次
※編集中
C# はGUIも作成できますが、本ページでは説明の初期のうちは、コンソールアプリ(DOSプロンプトのような画面にテキスト表示するプログラムのこと)のプログラムを解説したいと思います。
C++になくてC#にある機能は多々ありますが、特にC++との違いが大きい話題を挙げたいと思います。
変数の宣言
var
変数の宣言の際、intやcharなどの具体的な型を指定しなくても、宣言時の代入で具体的な数値または文字列の代入といっしょにvar とだけ指定すれば、代入されるデータから型を自動的に判断してくれます。なおvarとは「変数」を意味する英語 variable (ヴァリアブル)の略です。
using System; public class sample { public static void Main(string[] args) { var a = 4; Console.WriteLine(a); Console.WriteLine( a.GetType() ); var b = "hello"; Console.WriteLine(b); Console.WriteLine( b.GetType() ); var c = 1.52; Console.WriteLine(c); Console.WriteLine( c.GetType() ); var d = "w"; Console.WriteLine(d); Console.WriteLine( d.GetType() ); } }
- 実行結果
4 System.Int32 hello System.String 1.52 System.Double w System.String
C#9 以降、下記のように簡略できます。
var a = 4; Console.WriteLine(a); Console.WriteLine( a.GetType() ); var b = "hello"; Console.WriteLine(b); Console.WriteLine( b.GetType() ); var c = 1.52; Console.WriteLine(c); Console.WriteLine( c.GetType() ); var d = "w"; Console.WriteLine(d); Console.WriteLine( d.GetType() );
どちらにせよ、右辺値のない状態でvarキーワードを使うとエラーになり、コンパイルできません。varキーワードは右辺値から型を推論するキーワードなので、その右辺値そのものが無いと推論できないからです。
ほか、あるオブジェクトの型を取得するには、そのオブジェクトから .GetType() メソッドを使います。あるオブジェクトを指定してメソッドを使うには、オブジェクト名.メソッド名()
の表記で実行します。
似たようなメソッドとして typeof() というのがありますが、これはクラスに対して用いるものと、記法が異なるので、このセクションでのtypeofの説明は省略します。
var は型を右辺から推論してくれますが、しかし、いちどある変数の型が指定されたら、C#では、その変数の型は変更できません。
型を指定した宣言
varを使わずとも、下記のようにC言語と同様の従来の記法で変数宣言することもできます。
using System; public class sample { public static void Main(string[] args) { int a = 17; Console.WriteLine(a); Console.WriteLine( a.GetType() ); } }
- 実行結果
17 System.Int32
また、この例から分かるように int は System.Int32 と同じです。
なお、 int とは、「整数」を意味する英語 integer インテジャーの略です。
C# では、変数の型は、宣言後は(型を)変更できません。他のプログラミング言語だと、宣言時に数値を代入した変数にあとで文字列を代入できしまうのもあるが、C#ではそういうのは不可能です。
C# で宣言されている変数は、原則、どこかのクラスに所属していなければならない。C++のような感覚でもしコード冒頭のどこの class宣言もない部分で変数宣言するとエラーになりコンパイルできない。
// エラーになる using System; var a = 4; // 変数宣言がクラスに包まれていないのでエラーになる。 public class sample { public static void Main(string[] args) { Console.WriteLine(a); } }
- 実行結果
- エラーになりコンパイル不可
この仕様は明らかにC#やその手本のJavaが採用した「カプセル化」などの概念に基づいており、本質的な仕様であろう。「カプセル化」の概念とは、プログラムの部品をむきだしで管理することはせず、「カプセル」と言われるグループ単位でモジュール的・パッケージ的に管理することで大規模開発を効率化しようという1990~2001年頃の流行概念である。
つまり、C#には、C言語でいうところの「グローバル変数」は無い。開発元のマイクロソフトの公式ドキュメントでも、そう明言されていますMicrosoft Docs 日本語訳『C# のクラス、構造体、レコードの概要』2022/06/10 (2022年6月18日に確認)。
これはつまりC#では、すべての変数は、なんらかのクラスに属するメンバです。
もしC#でどこからもアクセスしたい変数を定義したい場合は(ただし同じソースコードファイル内)、下記コードのように単に public class でクラス宣言しておいて、必要に応じてクラス内の変数宣言で static 修飾子をつけて宣言すればいいだけである。
using System; // このクラス testval がどこからでもアクセスできて書き換え可能 public class testval{ public static int a = 3; public static int b =15; } public class sample { public static void Main(string[] args) { Console.WriteLine(testval.a); testval.a = 203; testval.b = 215; Console.WriteLine(testval.a); } }
- 実行結果
3 203
上記のような、どこからでもアクセスできるstatic変数をどう使うかについては、初心者には高度なので、クラスの節で別途に説明する。
別ファイルからのアクセスについては、初心者レベルを越えるので、当面の間、説明は省略する。
また、クラス側では変数宣言 var は不要です。新しく定義したクラス内で var で型推論をしようとするとエラーになります。
下記のように、「ローカル変数でないものをvarで宣言するな」(意訳)とコンパイラに叱られます。
test.cs(5,19): error CS0825: The contextual keyword `var' may only appear within a local variable declaration
このように、varには、型推論のほかにも、色々な意味があります。ローカル変数とはメソッド内(C言語でいう「関数」内)で定義されている変数のことです。
つまり、C++でいうところの、なんらかの「関数」内でないと(C#でいう「メソッド」内でないと)、varは使えません。
よく分からなければ、どこからでも出来るグローバル変数的なものをC#で作りたい場合、初心者のうちは、Main 関数内で変数宣言すれば、基本的に同じコードファイル中のどこからでもアクセスできるはずです。
グローバル変数的な管理用の public class などを別途に作るのは、慣れてきてからで十分です。
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 公式サイトを見てください。
書式指定
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 です。
条件分岐
単なるswitch文
まず、switch文やenumはC#では下記のように使う。.net core 6 で下記コードが動くので、C#10 に対応しているはず。
switch文についての説明は省略する。2022年6月現在、まだ当wikiにC#のif文やswitch文の教材は無いので、標準Cなどの解説を参照してもらいたい。
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 }
- 実行結果
土曜日
switch式
C#7~8 から「switch 式」というのが追加された。下記のような処理ができる。
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式の結果(右側)では、命令ではなく値だけを指定して事前に用意された変数に代入することになる。別途、その結果を代入した変数をつかって目的の処理をする命令が必要である。
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」(人)とかに変えてもコンパイル成功する。
念のため実行してみれば、
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 である。
レコードの各要素にアクセスするにはレコード名.要素名
でアクセスできる。しかし書き換えは出来ない。書き換えさえしなければ、閲覧などは出来る。
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
を使うことで可能です。
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 はそのままです。
メソッドとクラス
C# の「メソッド」と「クラス」の概念はお互いに関連しあっており、やや難しい。メソッドを読んでて分からなければ、とりあえずクラスも読んでみるのが良い。
メソッド
C言語でいう「関数」に似た機能として、c#では「メソッド」がある。
関数との違いとして、メソッドは必ずどこかのクラスまたは構造体に所属していなければならない。開発元のマイクロソフトの公式ドキュメントでも、そう明言されていますMicrosoft Docs 日本語訳『C# のクラス、構造体、レコードの概要』2022/06/10 (2022年6月18日に確認)。
つまり、必ずどこかのクラスまたは構造体のブロック中にてメソッドは宣言されることになる。どこのクラスにも属してない むきだしのメソッドを宣言しても、それがコード中に存在しているだけでコンパイルできずにエラーになる。
- うごくコード例
using System; // ユーザー定義側のメソッドには public はなくてもいい class testclass{ static public void testfunc (){ var a = 55; Console.WriteLine(a); } } public class sample { public static void Main(string[] args) { testclass.testfunc(); } }
- 実行結果
55
同じクラスの中にある関数を呼び出す場合なら、呼出時のクラス指定を省略できる。
using System; public class sample { public static void Main(string[] args) { testfunc(); } static public void testfunc (){ var a = 14; Console.WriteLine(a); } }
- 実行結果
14
なお、変数宣言で用いる var は、メソッド内でのローカル変数の宣言です。
ただし、Mainメソッドのブロック内のvarだけ、Mainメソッドよりも上位のメソッドは無いので結果的にMainメソッド内のvar宣言された変数があたかもグローバル変数のような働きをしますが、あくまで結果論です。
さて、C#のカプセル化などの理念により、すべての変数は、なんらかのクラスに所属していなければなりません。
変数宣言で static 修飾子をつけると、どこからでもアクセスできるし、どこでアクセスして読み書きしても結果は共有されます。このため、static修飾をつけることで、標準C や C++ でいうグローバル変数のように使えます。
下記コードでは、Mainメソッド以外のユーザ定義メソッドから書き換えをできるかのテストをしています。
- うごくコード例
using System; // 変数管理用 public class testval{ public static int a = 3; public static int b =15; // 使わないけど比較用 } // ユーザ定義メソッド class testclass{ static public void testfunc (){ // 書き換えテスト testval.a = 403; testval.b = 415; Console.WriteLine("ユーザー定義メソッドにて書き換え実行"); } } // Mainメソッド public class sample { public static void Main(string[] args) { Console.WriteLine("aは"); Console.WriteLine(testval.a); //test inst = new test(); Console.WriteLine("Main側での書き換えテスト"); testval.a = 203; //testval.b = 215; Console.WriteLine(testval.a); Console.WriteLine("これからユーザー定義メソッドでさらに書き換えを試みる"); testclass.testfunc(); Console.WriteLine("今、Mainに戻ったあとです"); Console.WriteLine("aは"); Console.WriteLine(testval.a); } }
- 実行結果
aは 3 Main関数側での書き換えテスト 203 これからユーザー定義関数でさらに書き換えを試みる ユーザー定義関数にて書き換え実行 今、Main関数に戻ったあとです aは 403
このように、staticで宣言された変数は、書き換えしたメソッドとは他のメソッドに移っても、書き換えの結果が保存されています。
「でもMainメソッドは、ユーザ定義メソッドの呼出元じゃないか? 呼出元以外ではどうなるんだ?」と疑問に思うなら、どうぞ試してください。結果は、Main以外の場所で結果確認をしても、上記コードと同様に書き換え結果は保存されます。
- うごくコード例
using System; // 変数管理用 public class testval{ public static int a = 3; public static int b =15; // 使わないけど比較用 } // ユーザ定義メソッド class testclass{ static public void testfunc (){ // 書き換えテスト testval.a = 403; testval.b = 415; Console.WriteLine("ユーザー定義メソッドにて書き換え実行"); } } // 結果確認用メソッド class testcheck{ static public void testfunc2 (){ Console.WriteLine("aは"); Console.WriteLine(testval.a); } } // Mainメソッド public class sample { public static void Main(string[] args) { Console.WriteLine("aは"); Console.WriteLine(testval.a); //test inst = new test(); Console.WriteLine("Main側での書き換えテスト"); testval.a = 203; //testval.b = 215; Console.WriteLine(testval.a); Console.WriteLine("これからユーザー定義メソッドでさらに書き換えを試みる"); testclass.testfunc(); Console.WriteLine("今、Mainに戻ったあとです"); Console.WriteLine("結果チェック用メソッドに移動します"); testcheck.testfunc2(); } }
- 実行結果
aは 3 Main側での書き換えテスト 203 これからユーザー定義メソッドでさらに書き換えを試みる ユーザー定義メソッドにて書き換え実行 今、Mainに戻ったあとです 結果チェック用メソッドに移動します aは 403
このように、とにかくstatic宣言された変数は、標準CやC++でいうグローバル変数的に振る舞います。
static 変数にアクセスする場合、インスタンスの作成は不要です。
クラス
C++でいうクラスは、データの集合をあらわすものの事である標準C言語とC++でいう「構造体」も、若干の違いはあるがクラスと似たようなものである。
だがC#は理念として、あらゆるものをクラスまたは構造体で管理するという方針があるので、C++的なクラスの性質だけでなく、C#特有の独特の性質がある。
基本
クラスの基本的な使いかたは下記のとおり。
コード例
using System;
class testclass {
public string name;
public int price;
}
public class sample {
public static void Main(string[] args) {
testclass a = new testclass() ;
a.name = "牛乳";
a.price = 140;
Console.WriteLine(a.name);
Console.WriteLine(a.price);
}
}
- 実行結果
牛乳 140
上記コードでいう string name;
や int price;
などをメンバという。C言語の構造体やC++の構造体/クラスでも同様の働きをするものを「メンバ」と言う。C#の用語はC++を踏襲したものである。
メンバは public にアクセス修飾子を指定しないかぎり、基本的には直接はアクセスできない。
コンストラクタ
下記コードのように、構造体またはクラス中に、構造体名あるいはクラス名と同じメソッド名をもつメソッドを書くと、その構造体/クラスを呼び出したときの処理を指定できる。
コード例
using System;
struct teststr {
public string name;
public int price;
public teststr(string a, int b){
name = a;
price = b;
}
}
public class sample {
public static void Main(string[] args) {
teststr milk = new teststr("牛乳", 150);
Console.WriteLine(milk.name);
Console.WriteLine(milk.price);
Console.WriteLine();
teststr cmilk = new teststr("コーヒー牛乳", 180);
Console.WriteLine(cmilk.name);
Console.WriteLine(cmilk.price);
Console.WriteLine();
Console.WriteLine(milk.name);
Console.WriteLine(milk.price);
Console.WriteLine();
}
}
- 実行結果
牛乳 150 コーヒー牛乳 180 牛乳 150
クラスと構造体の違い
論より証拠、実際に構造体とクラスのコードをそれぞれ実行して結果を比べてみましょう。
まず、milk.price = 150;
のようにメンバに直接に代入する場合は、クラスでも構造体でも結果は下記のように同じです。
コード例
using System;
struct teststr {
public string name;
public int price;
}
public class sample {
public static void Main(string[] args) {
teststr milk = new teststr();
milk.name = "牛乳";
milk.price = 150;
teststr cmilk = new teststr();
cmilk.name = "コーヒー牛乳";
cmilk.price = 180;
Console.WriteLine(cmilk.name);
Console.WriteLine(cmilk.price);
}
}
- 実行結果
コーヒー牛乳 180
コード例
using System;
class testclass {
public string name;
public int price;
}
public class sample {
public static void Main(string[] args) {
testclass milk = new testclass();
milk.name = "牛乳";
milk.price = 150;
testclass cmilk = new testclass();
cmilk.name = "コーヒー牛乳";
cmilk.price = 180;
Console.WriteLine(cmilk.name);
Console.WriteLine(cmilk.price);
}
}
- 実行結果
コーヒー牛乳 180
では違いはというと、・・・※調査中