C Sharp

出典: フリー教科書『ウィキブックス(Wikibooks)』
ナビゲーションに移動 検索に移動
Wikipedia
ウィキペディアC Sharpの記事があります。

本書は、マイクロソフト社が開発したプログラミング言語「C#」に関するものです。C#は、先行するCC++Javaと同じファミリーに属するの構文と、 マイクロソフト社の社員が.NET Foundationを介して開発し、MITライセンスの下でリリースされている.NETという共通言語基盤( Common Language Infrastructure ; CLI )で動作することが特徴です。

目次[編集]

※ 以下、サブページ未作成

  • C Sharp/ループや繰り返し

※編集中[編集]

C# はGUIも作成できますが、本ページでは説明の初期のうちは、コンソールアプリ(コマンド プロンプトのような画面にテキスト表示するプログラムのこと)のプログラムを解説したいと思います。

C++になくてC#にある機能は多々ありますが、特にC++との違いが大きい話題を挙げたいと思います。

C# の標準規格
プログラミング言語C#は、2000年にMicrosoft社のAnders Hejlsberg氏によって設計され、その後2002年にEcma(ECMA-334)、2003年にISO/IEC(ISO/IEC 23270)によって国際標準として承認されました。

2022年6月時点のC#標準

  • ECMA-334:2022(VC#6.0相当)
  • ISO/IEC 23270:2018(VC#5.0相当)
  • JIS X 3015:2008『プログラム言語C#』(VC#2.0相当)

C#には以下のような独立した実装があります。

Roslyn
Microsoftが開発したApacheライセンスのオープンソースプロジェクト。GitHubで公開されています。Visual Studio 2015 以降のVC#。
クローズドなVC#
Visual Studio 2013 まで。
シェアードソース共通言語基盤のC#コンパイラ
共通言語基盤 (CLI) とともにがソースコードが公開されたC#コンパイラ。
Mono Compiler Suite(mcs)
Mono Projectが開発したMonoの一部を構成します。
the C-Sharp code compiler (cscc)
DotGNU Projectが開発したPortable.NETの一部を構成します。

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) ; 

のようになります。

書式指定[編集]

C# の stringクラスのFormat静的メソッド では、表示位置の指定には、引用符中での波カッコ { } を使います

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 です。

配列[編集]

同じ型のデータを番号をつけて、ひとまとめにしたものを配列(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

foraech[編集]

マイクロソフトの公式サイトにもありますが、たとえば foreach という機能が、C++ ではなかなか入門レベルでは見かけず独特です。 これは、C++11の拡張for文に相当しますが、構文は幾分異なります。

以下、「Using foreach with arrays (C# Programming Guide)』(マイクロソフト)09/15/2021(access-date:2022-07-11)を抜粋にしたものです。

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 を使わずとも、C言語的な従来の記法でもfor文により、同一の処理は可能です。

using System;

public class Hello {
  public static void Main() {
   int[] numbers = { 4, 5, 6, 1, 2, 3, -2, -1, 0 };

    for (int i = 0; i < numbers.Length; i++) {
      System.Console.Write("{0} ", numbers[i]);
    }
    System.Console.Write("\n");
  }
}
実行結果
4 5 6 1 2 3 -2 -1 0

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#で配列化するような大量の記録をあつかう場合には、レコード型ではなく構造体やクラスによるインスタンスを配列化したものを使うのが良いでしょう。