C# の基礎知識
●基本的なデータ型
- C# のデータ型は「値型」と「参照型」の二種類がある
- 値型はデータを直接変数 (メモリ) に保持する (数, 文字, 真偽値, 構造体など)
- 参照型はデータへの参照情報 (アドレス) を変数に保持する (文字列, 配列, オブジェクトなど)
- 数
- sbyte, short, int, long (8, 16, 32, 64 bit 符号付き整数)
- byte, ushort, uint, ulong (8, 16, 32, 64 bit 無符号整数)
- float (32 bit 浮動小数点数)
- double (64 bit 浮動小数点数)
- decimal (10 進小数)
- 文字 char, ' で囲む ('a', 'A' など)
- 真偽値 bool (true, false)
- 文字列 string, " で囲む (演算子 + で文字列の連結ができる)
- 配列
- 配列の定義
- データ型[] 変数名 = new データ型 [大きさ];
- データ型[] 変数名 = new データ型 [] {値1, ..., 値N};
- データ型[] 変数名 = {値1, ..., 値N};
- 要素のアクセスは角カッコ [ ] を使う
- 添字は 0 から始まる
- 配列の大きさは 変数名.Length で求めることができる
- 二次元配列の定義
- データ型[,] 変数名 = new データ型 [大きさ1, 大きさ2];
- データ型[,] 変数名 = new データ型 [,] {{値1, ..}, {値2, ...}};
- データ型[,] 変数名 = {{値1, ...}, {値2, ...}};
- 要素のアクセスは角カッコの中をカンマ ( , ) で区切る
- 多次元配列は角カッコの中を次元数の数だけカンマで区切る
- 多次元配列の場合、Length は配列全体の大きさになる
- 各次元の大きさはメソッド GetLenght() で求めることができる
- 配列の配列 (ジャグ配列) も定義できる
- データ型 [][] 変数名 = new データ型[大きさ][]
csharp> int[] a = new int[10];
csharp> a[0];
0
csharp> a[9] = 1;
csharp> a[9];
1
csharp> int[,] b = {{1,2,3},{4,5,6},{7,8,9}};
csharp> b[0,0];
1
csharp> b[2,2];
9
csharp> b[2,2] = 10;
csharp> b[2,2];
10
csharp> var c = new int[3, 4];
csharp> c.Length;
12
csharp> c.GetLength(0);
3
csharp> c.GetLength(1);
4
●基本的な演算子
- 算術演算子 (+, -, *, /, %)
- 比較演算子 (==, !=, <, >, <=, >=)
- 論理演算子 (!, &&, ||)
- ビット演算子 (~, &, |, ^, <<, >>)
- 代入演算子 (=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=)
- インクリメント (++)
- デクリメント (--)
- 三項演算子 (test ? true節 : else節)
●基本的な制御構造
- if (test1) { then節; ... } else if (test2) { then節2; ... } else { else節; ... }
- switch(変数) { case 値1: 処理1; ...; break; ... default: 処理; ... }
- C# の switch はフォールスルーを禁止している
- ただし、case 1: case 2: のように連続して書くことはできる
- while (test) { 処理; ... }
- do { 処理; ... } while (test);
- for (初期化式; 条件式; 更新式) { 処理; ... }
- foreach(データ型 変数 in collection) { 処理; ... }
- 繰り返しの制御に break と continue が使える
●関数
- C# の場合、関数はメソッド (method) のことで、クラス (class) の中で定義する
- 関数のように使いたい場合は static を付ける (静的メソッド)
- static 返り値のデータ型 関数名(データ型 仮引数名, ...) { 処理; ...; return 返り値; }
- 返り値が無い場合はデータ型を void にする
- 関数呼び出しは 関数名(実引数, ...)
- 他のクラスから呼び出す場合は public も付ける
- この場合、関数呼び出しは クラス名.関数名(実引数, ...) になる
- 関数名は Pascal 形式 (各単語の頭文字を大文字にしてつなげる) で記述する
- C# の関数 (メソッド) は多重定義 (オーバーロード) が可能
- 引数名 (変数名) は Camel 形式 (先頭文字を小文字、つなげる単語の頭文字を大文字) で記述する
- 可変長引数の定義は データ型 関数名(..., params 配列型 仮引数名) { ... }
- 可変長引数に配列を渡すと、その配列を展開して関数に渡す
- オプション引数の定義は データ型 関数名(..., データ型 仮引数名 = 値) { ... }
- 関数を呼び出すとき、名前付き引数 (仮引数名: 値, ...) を使用できる
- 末尾再帰最適化はサポートされていないようだ
リスト : 階乗とフィボナッチ関数
using System;
class Test {
// 再帰
static long Fact(long n) {
return n == 0 ? 1 : n * Fact(n - 1);
}
// 繰り返し
static long Facti(long n) {
long a = 1;
for (long i = 2; i <= n; i++) a *= i;
return a;
}
// 再帰
static int Fibo(int n) {
if (n < 2) return n;
return Fibo(n - 2) + Fibo(n - 1);
}
// 繰り返し
static int Fiboi(int n) {
int a = 0, b = 1;
while (n-- > 0) {
int c = a + b;
a = b;
b = c;
}
return a;
}
static void Main() {
Console.WriteLine("{0}! = {1}", 10, Fact(10));
Console.WriteLine("{0}! = {1}", 15, Fact(15));
Console.WriteLine("{0}! = {1}", 20, Fact(20));
Console.WriteLine("{0}! = {1}", 10, Facti(10));
Console.WriteLine("{0}! = {1}", 15, Facti(15));
Console.WriteLine("{0}! = {1}", 20, Facti(20));
Console.WriteLine("Fibo({0}) = {1}", 10, Fibo(10));
Console.WriteLine("Fibo({0}) = {1}", 15, Fibo(15));
Console.WriteLine("Fibo({0}) = {1}", 20, Fibo(20));
Console.WriteLine("Fiboi({0}) = {1}", 10, Fiboi(10));
Console.WriteLine("Fiboi({0}) = {1}", 15, Fiboi(15));
Console.WriteLine("Fiboi({0}) = {1}", 20, Fiboi(20));
}
}
C>factfibo
10! = 3628800
15! = 1307674368000
20! = 2432902008176640000
10! = 3628800
15! = 1307674368000
20! = 2432902008176640000
Fibo(10) = 55
Fibo(15) = 610
Fibo(20) = 6765
Fiboi(10) = 55
Fiboi(15) = 610
Fiboi(20) = 6765
●変数
- 変数宣言は データ型 変数名; または データ型 変数名 = 初期値;
- 変数名は Camel 形式 (先頭文字を小文字、つなげる単語の頭文字を大文字) で記述する
- データ型に var を指定するとコンパイラがデータ型を決定する (型推論)
- 変数の有効範囲は宣言されているブロック { ... } の中
- ブロックが入れ子の場合、内側のブロックで外側のブロックと同名の変数を宣言することはできない
- C# の場合、グローバル変数は基本的に定義できない (代替の方法はある)
●クラス
- クラス定義は class クラス名 { ... }
- クラス内で宣言された変数を「フィールド (field)」とか「メンバ変数」という
- クラス内で定義された関数を「メソッド (method)」という
- メソッドに static を付けると「静的メソッド」になる
- 他のプログラミング言語では「クラスメソッド」と呼ばれることもある
- 静的メソッドは クラス名.メソッド名(...) で呼び出す
- フィールドに static を付けると「静的フィールド」になる
- 他のプログラミング言語では「クラス変数」と呼ばれることもある
- 静的フィールドは クラス名.フィールド名 でアクセスする
- フィールドやメソッドにはアクセス修飾子をつけることができる
- public, protected, internal, private
- 省略した場合は private
- インスタンスの生成は new クラス名() で行う
- このときクラスと同名のメソッド (コンストラクタ) が呼び出される
- アクセス修飾子 クラス名(データ型 仮引数, ...) { ... }
- コンストラクタは値を返さない (void はつけない)
- コンストラクタも多重定義できる
- static を付けると「静的コンストラクタ」になる
- 静的コンストラクタはクラスを初めて使うときに一度だけ呼び出される
- インスタンスを obj とすると、フィールドのアクセスは obj.field名, メソッドの呼び出しは obj.メソッド名(実引数, ...)
- メソッドの中で変数 this は自分自身 (インスタンス) を表す
- 不要になったインスタンスは GC (ガベージコレクション) により回収される
- このとき、デストラクタが呼び出される
- ~クラス名() { ... }
- 引数と返り値はない
- class の前に static を付けると「静的クラス」になる
- 静的クラスで定義できるのは静的なフィールドやメソッドだけ
- 静的クラスはインスタンスを生成できない
- using static クラス名; とすると、静的フィールドや静的メソッドの指定でクラス名を省略できる
リスト : 簡単な例題 (Point, Point3D クラス)
using System;
class Point {
double x = 0.0, y = 0.0;
// コンストラクタ
public Point() { }
public Point(double x, double y) {
this.x = x;
this.y = y;
}
// メソッド
public double Distance(Point p) {
double dx = x - p.x;
double dy = y - p.y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
class Point3D {
double x = 0.0, y = 0.0, z = 0.0;
// コンストラクタ
public Point3D() { }
public Point3D(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
// メソッド
public double Distance(Point3D p) {
double dx = x - p.x;
double dy = y - p.y;
double dz = z - p.z;
return Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
}
class Test {
static void Main() {
var p1 = new Point();
var p2 = new Point(1.0, 1.0);
Console.WriteLine("{0}", p1.Distance(p2));
var p3 = new Point3D();
var p4 = new Point3D(1.0, 1.0, 1.0);
Console.WriteLine("{0}", p3.Distance(p4));
}
}
C>point
1.4142135623731
1.73205080756888
●継承
- C# のクラスは他のクラスのフィールドやメソッドを「継承」することができる
- 元になるクラスを「スーパークラス」、継承したクラスを「サブクラス」とか「派生クラス」という
- C# は単一継承なのでスーパークラスは一つだけ指定できる
- class クラス名 : スーパークラス { ... }
- スーパークラスの指定を省略すると暗黙のうちにクラス object を継承する
- 「インターフェース (interface)」はいくつでも継承できる
- コンストラクタは最上位のスーパークラスから順番に呼び出される
- このとき呼び出されるのは引数なしのコンストラクタ
- スーパークラスのコンストラクタは base を使って明示的に呼び出すこともできる
- public クラス名(引数, ...) : base(引数, ...) { ... }
- サブクラスでスーパークラスのメソッドと同名のメソッドを定義することができる
- これを「オーバーライド (over ride)」という
- C# の場合、オーバーライドする時は修飾子 new を付ける
- public new type MethodName(...) { ... }
- new をつけないとコンパイラが警告をだす
- オーバーライドしたメソッドからスーパークラスのメソッドを呼び出すときは base を使う
- base.MethodName(...); // スーパークラスの MethodName を呼び出す
- 継承を禁止する場合は class の前に修飾子 sealed を付ける
リスト : 継承の簡単なサンプル
using System;
class Foo {
int x = 0, y = 0;
public Foo() {}
public Foo(int a, int b) {
x = a;
y = b;
}
// アクセスメソッド
public int GetX() { return x; }
public int GetY() { return y; }
public void SetX(int a) { x = a; }
public void SetY(int b) { y = b; }
// 合計値を求める
public int Sum() {
return x + y;
}
}
class Bar : Foo {
int z = 0;
public Bar() { }
public Bar(int a, int b, int c) : base(a, b) {
z = c;
}
// アクセスメソッド
public int GetZ() { return z; }
public void SetZ(int c) { z = c; }
// 合計値を求める
public new int Sum() {
return z + base.Sum();
}
}
class Test {
static void Main() {
var a = new Foo(1, 2);
var b = new Bar(10, 20, 30);
Console.WriteLine("{0}", a.Sum()); // 3 と表示
Console.WriteLine("{0}", b.Sum()); // 60 と表示
}
}
C>test
3
60
●仮想関数とポリモーフィズム
- サブクラスのインスタンスはスーパークラスの変数に代入することができる
- これを「アップキャスト」という
- アップキャストした場合、スーパークラスと同じデータ型として扱われるため、「ポリモーフィズム」が機能しない
- ポリモーフィズムを機能させるには「仮想関数」を使う
- public virtual データ型 メソッド名(...) { ... }
- virtual を付けると仮想関数になる
- サブクラスでオーバーライドするメソッドは override を付ける
- public override データ型 メソッド名(...) { ... }
リスト : 仮想関数の簡単な例題
using System;
class Foo {
int x = 0, y = 0;
public Foo() {}
public Foo(int a, int b) {
x = a;
y = b;
}
// アクセスメソッド
public int GetX() { return x; }
public int GetY() { return y; }
public void SetX(int a) { x = a; }
public void SetY(int b) { y = b; }
// 合計値を求める
public virtual int Sum() {
return x + y;
}
}
class Bar : Foo {
int z = 0;
public Bar() { }
public Bar(int a, int b, int c) : base(a, b) {
z = c;
}
// アクセスメソッド
public int GetZ() { return z; }
public void SetZ(int c) { z = c; }
// 合計値を求める
public override int Sum() {
return z + base.Sum();
}
}
class Test {
static void Main() {
Foo a = new Foo(1, 2);
Foo b = new Bar(10, 20, 30); // アップキャスト
Console.WriteLine("{0}", a.Sum()); // 3 と表示
Console.WriteLine("{0}", b.Sum()); // 60 と表示
}
}
●抽象クラスと抽象メソッド
リスト : 抽象クラスと抽象メソッドの簡単な例題
using System;
abstract class Figure {
public abstract string KindOf();
public abstract double Area();
public void Print(){
Console.WriteLine("{0}: area = {1}", KindOf(), Area());
}
}
// 三角形
class Triangle : Figure {
double altitude, base_line;
public Triangle(double a, double b){
altitude = a;
base_line = b;
}
public override string KindOf(){
return "Triangle";
}
public override double Area(){
return altitude * base_line / 2.0;
}
}
// 四角形
class Rectangle : Figure {
double width, height;
public Rectangle(double w, double h){
width = w;
height = h;
}
public override string KindOf(){
return "Rectangle";
}
public override double Area(){
return width * height;
}
}
// 円
class Circle : Figure {
double radius;
public Circle(double r){
radius = r;
}
public override string KindOf(){
return "Circle";
}
public override double Area(){
return radius * radius * Math.PI;
}
}
class Test {
static void Main() {
Triangle a = new Triangle(2.0, 2.0);
Rectangle b = new Rectangle(2.0, 2.0);
Circle c = new Circle(2.0);
a.Print();
b.Print();
c.Print();
Figure[] figTable = {
new Triangle(3.0, 3.0),
new Rectangle(3.0, 3.0),
new Circle(3.0),
};
foreach(Figure f in figTable) {
f.Print();
}
}
}
C>figure
Triangle: area = 2
Rectangle: area = 4
Circle: area = 12.5663706143592
Triangle: area = 4.5
Rectangle: area = 9
Circle: area = 28.2743338823081
●インターフェース
- インターフェース (interface) はメソッドの仕様 (宣言) だけを記述した抽象クラス
- interface インターフェース名 { メソッドの宣言; ... }
- 宣言されたメソッドは public abstract になる
- メソッドはインターフェースを継承したサブクラスで実装する
- class クラス名 : スーパークラス, インターフェース1, ... { メソッドの実装; ... }
- インターフェースはいくつでも継承することができる
- インターフェースもデータ型として使用できる (アップキャスト可能)
リスト : インターフェースの簡単な使用例
using System;
// インターフェースの定義
interface Figure {
string KindOf();
double Area();
void Print();
}
// 三角形
class Triangle : Figure {
double altitude, base_line;
public Triangle(double a, double b){
altitude = a;
base_line = b;
}
public string KindOf(){
return "Triangle";
}
public double Area(){
return altitude * base_line / 2.0;
}
public void Print(){
Console.WriteLine("{0}: area = {1}", KindOf(), Area());
}
}
// 四角形
class Rectangle : Figure {
double width, height;
public Rectangle(double w, double h){
width = w;
height = h;
}
public string KindOf(){
return "Rectangle";
}
public double Area(){
return width * height;
}
public void Print(){
Console.WriteLine("{0}: area = {1}", KindOf(), Area());
}
}
// 円
class Circle : Figure {
double radius;
public Circle(double r){
radius = r;
}
public string KindOf(){
return "Circle";
}
public double Area(){
return radius * radius * Math.PI;
}
public void Print(){
Console.WriteLine("{0}: area = {1}", KindOf(), Area());
}
}
class Test {
static void Main() {
Triangle a = new Triangle(2.0, 2.0);
Rectangle b = new Rectangle(2.0, 2.0);
Circle c = new Circle(2.0);
a.Print();
b.Print();
c.Print();
Figure[] figTable = {
new Triangle(3.0, 3.0),
new Rectangle(3.0, 3.0),
new Circle(3.0),
};
foreach(Figure f in figTable) {
f.Print();
}
}
}
●プロパティ
リスト : プロパティの簡単な例題
using System;
class Foo {
public int X { set; get; }
public int Y { set; get; }
// 合計値を求める
public int Sum {
get { return X + Y; }
}
}
class Test {
static void Main() {
var a = new Foo();
a.X = 10;
a.Y = 20;
Console.WriteLine("{0}", a.Sum); // 30
}
}
リスト : getter だけ自動生成する
using System;
class Foo {
public Foo(int a, int b) {
X = a;
Y = b;
}
public int X { get; }
public int Y { get; }
// 合計値を求める
public int Sum {
get { return X + Y; }
}
}
class Test {
static void Main() {
var a = new Foo(10, 20);
Console.WriteLine("{0}", a.Sum); // 30
}
}
●ジェネリック
- ジェネリック (generics) はデータ型をパラメータ化する機能のこと
- 型パラメータ (型引数) は <T, U, V, ...> のように < > の中で指定する
- ジェネリックを使ってメソッドとクラスを定義できる
- データ型 メソッド名<T, ...>(仮引数, ...) where 制約条件 { ... }
- class クラス名<T, ...> where 制約条件 { ... }
- 制約条件の指定方法
- where T : struct (型 T は値型)
- where T : class (型 T は値型)
- where T : new() (型 T は引数なしのコンストラクタを持つ)
- where T : class_name (型 T はクラス class_name を継承する)
- where T : interface_name (型 T はインターフェース interface_name を実装している)
- データ型は クラス名<データ型, ...> になる
- インスタンスの生成は new クラス名<データ型, ...>(実引数, ...)
- メソッドの呼び出しは メソッド名<データ型, ...>(実引数, ...)
- 実引数から型変数のデータ型が特定できる場合、型変数の指定は省略できる
リスト : ジェネリッククラスの簡単な使用例
using System;
class Foo<T> {
T x;
public Foo() { x = default(T); }
public Foo(T n) { x = n; }
public T Get() { return x; }
};
class Bar<T> where T : new() {
T y;
public Bar() { y = new T(); }
public Bar(T n) { y = n; }
public T Get() { return y; }
};
class Baz {
int z;
public Baz() { z = 123; }
public Baz(int n) { z = n; }
public int Get() { return z; }
};
class Test {
static void Main() {
var a = new Foo<int>();
var b = new Foo<double>(1.2345);
var c = new Bar<Baz>();
Console.WriteLine("{0}", a.Get()); // 0
Console.WriteLine("{0}", b.Get()); // 1.2345
Console.WriteLine("{0}", c.Get().Get()); // 123
}
}
リスト : ジェネリックメソッドの簡単な使用例
using System;
class Test {
// 型変数の場合、比較演算子は使えないので、
// インターフェース IComparable を使う
static int IndexOf<T>(T[] buff, T x) where T : IComparable {
for (int i = 0; i < buff.Length; i++) {
if (x.CompareTo(buff[i]) == 0) return i;
}
return -1;
}
static void Main() {
int[] a = {1,2,3,4,5,6,7,8};
double[] b = {1.1, 2.2, 3.3, 4.4, 5.5};
Console.WriteLine("{0}", IndexOf(a, 5)); // 4
Console.WriteLine("{0}", IndexOf(a, 9)); // -1
Console.WriteLine("{0}", IndexOf(b, 4.4)); // 3
Console.WriteLine("{0}", IndexOf(b, 5.0)); // -1
}
}
●構造体
- 構造体はクラスのようにユーザーが定義するデータ型
- struct 構造体名 : インターフェース, ... { ... }
- クラスは参照型になるが構造体は値型になる
- 変数に代入する (または引数に渡す) ときは値がコピーされる
- 構造体は引数なしのコンストラクタやデストラクタを定義することができない
- 引数があるコンストラクタやメソッドは定義できる
- 構造体はクラスや他の構造体を継承することはできない
- 構造体を継承してサブクラスを定義することもできない
- インターフェースは継承することができる
- データの生成は new 構造体名() で行う
- 引数なしで構造体を生成した場合、値型のフィールドは 0 に、参照型のフィールドは null に初期化される
- ジェネリックも使用できる
リスト : 構造体の簡単な使用例
using System;
struct Point {
double x, y;
public Point(double a, double b) {
x = a;
y = b;
}
public double Distance(Point p) {
double dx = x - p.x;
double dy = y - p.y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
class Test {
static void Main() {
Point p1 = new Point();
Point p2 = new Point(1.0, 1.0);
Console.WriteLine("{0}", p1.Distance(p2)); // 1.4142135623731
}
}
リスト : 構造体とジェネリックの簡単な使用例
using System;
struct Pair<T, U> {
T p;
U q;
public Pair(T a, U b) {
p = a;
q = b;
}
public T Fst() { return p; }
public U Snd() { return q; }
};
class Test {
static void Main() {
var a = new Pair<string, int>("foo", 10);
var b = new Pair<string, double>("bar", 1.2345);
Console.WriteLine("{0}, {1}", a.Fst(), a.Snd()); // foo, 10
Console.WriteLine("{0}, {1}", b.Fst(), b.Snd()); // bar, 1.2345
}
}
●デリゲートとラムダ式
- C# のデリゲート (delegate) はメソッドへの参照を表すデータ型を定義する
- delegate 返り値のデータ型 型名(データ型 仮引数, ...);
- C/C++の「関数へのポインタ」と似ている
- デリゲートを使ってメソッドを変数に代入したり関数の引数に渡すことができる
リスト : デリゲートの簡単な使用例
using System;
class Test {
// int を受け取って int を返すメソッドの型 IntFunc を定義
delegate int IntFunc(int x);
// マッピング (intFunc 型のメソッドを受け取る高階関数)
static int[] Map(IntFunc func, int[] xs) {
int[] ys = new int[xs.Length];
for (int i = 0; i < xs.Length; i++)
ys[i] = func(xs[i]);
return ys;
}
// 引数を二乗するメソッド
static int Square(int x) { return x * x; }
static void Main() {
int[] a = {1, 2, 3, 4, 5};
foreach(int x in Map(Square, a))
Console.Write("{0} ", x); // 1 4 9 16 25
Console.WriteLine("");
}
}
- デリゲートはジェネリックを使って定義することができる
- delegate T FuncOne<T>(T x);
- static int[] Map(FuncOne<int> func, int[] xs) { ... }
- C# には汎用のデリゲート Func<T, U, ..., Result> と Action<T, U, ...> が用意されている
- Func は T, U, ... が仮引数の型、Result が返り値の型を表す
- static int[] Map(Func<int,int> func, int[] xs) { ... }
- 返り値が無い場合は Action を使う
- C# はデリゲートを使って匿名関数を定義できるが、今は「ラムダ式」を使ったほうが簡単
- ラムダ式の定義は (データ型 仮引数, ...) => { 処理; ...; return 値; }
- (仮引数, ...) => 式, 仮引数 => 式 とすることもできる (型推論が行われる)
リスト : ラムダ式の簡単な使用例
using System;
class Test {
// 配列のマッピング
static T[] Map<T>(Func<T, T> func, T[] xs) {
T[] ys = new T[xs.Length];
for (int i = 0; i < xs.Length; i++)
ys[i] = func(xs[i]);
return ys;
}
static void Main() {
int[] a = {1, 2, 3, 4, 5};
double[] b = {1.1, 2.2, 3.3, 4.4, 5.5};
foreach(int x in Map(n => n * n, a))
Console.Write("{0} ", x); // 1 4 9 16 25
Console.WriteLine("");
foreach(double x in Map(n => n * n, b))
Console.Write("{0} ", x); // 1.21 4.84 10.89 19.36 30.25
Console.WriteLine("");
}
}
- 「クロージャ (closure)」もサポートされている
csharp> Func<int, Func<int, int>> make_adder = n => x => n + x;
csharp> Func<int, int> add10 = make_adder(10);
csharp> add10(1);
11
csharp> add10(20);
30
- デリゲートには演算子 += を使って複数のメソッドを代入することができる
- これを「マルチキャストデリゲート」という
- メソッドは代入した順番に実行される
csharp> Action<string> greeting = mes => Console.WriteLine("hello, {0}", mes);
csharp> greeting("M.Hiroi");
hello, M.Hiroi
csharp> greeting += mes => Console.WriteLine("good by, {0}", mes);
csharp> greeting("M.Hiroi");
hello, M.Hiroi
good by, M.Hiroi
●例外処理
- 例外 (Exception) は throw 文で送出する
- throw 文で送出できるデータ型は System.Exception (例外クラス) を継承したクラス
- 例外の捕捉は try - chach - finally 文を使う
try {
...
} catch(例外クラス [引数]) {
...
} finally {
...
}
- catch 節には捕捉する例外クラスを指定する
- その引数には例外クラスのインスタンスがセットされる (引数は省略可)
- catch 節はいくつでも指定することができる
- finally 節は try 文の処理で例外が発生したかどうかにかかわらず、try 文の処理が終了するときに必ず実行される
csharp> class Foo : Exception {};
csharp> try { throw new Foo(); } catch(Foo) { Console.WriteLine("catch Foo"); };
catch Foo
csharp> try { Console.WriteLine("foo"); } finally { Console.WriteLine("oops!");
foo
oops!
csharp> try { throw new Foo(); } catch(Foo) { Console.WriteLine("catch Foo"); } finally { Console.WriteLine("oops!"); };
catch Foo
oops!
●可変長配列
- C# のライブラリには ArrayList と List<T> という可変長配列が用意されている
- ここではジェネリックの List<T> の基本的な操作を簡単に説明する
- List<T> は連結リストではない (LinkedList<T> が双方向リスト)
- ジェネリックコレクションを使うときは using System.Collections.Generic;
- コンストラクタ
- List<T>(); 空の配列を生成
- List<T>(collection); collection の要素を格納した配列を生成
- List<T>(size); 容量が size の配列を生成 (要素数は 0)
- プロパティ
- Capacity : 容量の取得と設定
- Count : 要素数の取得
- list[n] : n 番目の要素にアクセスする
- 基本的なメソッド
- Add(item) 配列の末尾に item を追加
- Clear() 配列を空にする
- Contains(item) 配列の中に item があれば true を返す
- Insert(n, item) 配列の n 番目に item を挿入
- RemoveAt(n) 配列の n 番目の要素を削除
- この他にも探索やソートなど便利なメソッドが多数用意されている
csharp> var a = new List<int>();
csharp> for (int i = 0; i < 10; i++) a.Add(i);
csharp> a
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
csharp> a.Insert(0, -1);
csharp> a
{ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
csharp> a.RemoveAt(0);
csharp> a
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
csharp> a.Count;
10
csharp> a.Capacity;
16
csharp> a.RemoveAt(a.Count - 1);
csharp> a
{ 0, 1, 2, 3, 4, 5, 6, 7, 8 }
●連想配列 (ハッシュ)
- C# のライブラリには HashTable と Dictionary<K, V> という連想配列 (ハッシュ) が用意されている
- ここではジェネリックの Dictionary<K, V> の基本的な操作を簡単に説明する
- ジェネリックコレクション を使うときは using System.Collections.Generic;
- コンストラクタ
- Dictionary<K, V>(); 空のハッシュを生成
- プロパティ
- Count : 要素数を取得する
- hash[key] : キー key の値を取得する (キーが見つからない場合はエラー)
- hash[key] = val; : キー key の値を val に更新する
- Keys : キーのコレクションを取得する
- Values : 値のコレクションを取得する
- 基本的なメソッド
- Add(key, val) key と val をハッシュに追加 (hash[key] = val でも OK)
- Clear() ハッシュを空にする
- ContainsKey(key) ハッシュの中にキー key があれば true を返す
- ContainsValue(val) ハッシュの中に値 val があれば true を返す
- Remove(key) ハッシュのキー key とその値を削除
- 削除できた場合は true, キーが見つからない場合は false を返す
csharp> var a = new Dictionary();
csharp> a["foo"] = 10;
csharp> a["baz"] = 20;
csharp> a["bar"] = 30;
csharp> a["oops"] = 40;
csharp> a;
{{ "foo", 10 }, { "baz", 20 }, { "bar", 30 }, { "oops", 40 }}
csharp> a.ContainsKey("foo");
true
csharp> a.ContainsKey("Foo");
false
csharp> a.Keys;
{ "foo", "baz", "bar", "oops" }
csharp> a.Remove("foo");
true
csharp> a;
{{ "baz", 20 }, { "bar", 30 }, { "oops", 40 }}
csharp> a.Remove("foo");
false
csharp> a.Remove("Foo");
false
●イテレータ
- インターフェース IEnumerable のメソッド GetEnumerator() を実装すると foreach を使用することができる
- GetEnumerator() はインターフェース IEnumerator を継承したクラスのインスタンスを返す
- これがいわゆる「イテレータ (iterator)」として動作する
- IEnumerator を継承する場合、次に示すプロパティとメソッドを実装する
- Current イテレータが示す要素を取得する
- bool MoveNext() イテレータを次の要素へ進める
- void Reset() イテレータを初期値に戻す
- C# は ver 2.0 以降になると、yield return を使ってイテレータを簡単に作ることができる
- なお、C# では yield return による機能を「イテレータ」と呼んでいる
- GetEnumerator() の中で yiled return 要素; を実行するだけ
- イテレータを途中で中断するときは yield break を使う
リスト : イテレータの簡単な例題
using System;
using System.Collections;
class Foo : IEnumerable {
int a, b, c;
public Foo(int x, int y, int z) {
a = x;
b = y;
c = z;
}
public IEnumerator GetEnumerator() {
yield return a;
yield return b;
yield return c;
}
}
class Test {
static void Main() {
var a = new Foo(1, 2, 3);
foreach(int n in a) {
Console.WriteLine("{0}", n);
}
}
}
C>test
1
2
3
- ジェネリックの場合は IEnumerable<T> を継承する
- 実装するメソッドは IEnumerator<T> GetEnumerator()
- もうひとつ IEnumerator IEnumerable.GetEnumerator() も実装すること
- これを実装すると後述する LINQ のメソッドや高階関数を利用できるようになる
リスト : ジェネリック版
using System;
using System.Collections;
using System.Collections.Generic;
class Foo<T> : IEnumerable<T> {
T a, b, c;
public Foo(T x, T y, T z) {
a = x;
b = y;
c = z;
}
public IEnumerator<T> GetEnumerator() {
yield return a;
yield return b;
yield return c;
}
// public は付けない
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
class Test {
static void Main() {
var a = new Foo<int>(1, 2, 3);
foreach(int n in a) {
Console.WriteLine("{0}", n);
}
}
}
C>test
1
2
3
●インデクサー
リスト : インデクサーの簡単な例題
using System;
class Foo {
int a, b, c;
public Foo(int x, int y, int z) {
a = x;
b = y;
c = z;
}
public int this[int n] {
set {
switch(n) {
case 0: a = value; break;
case 1: b = value; break;
default: c = value; break;
}
}
get {
switch(n) {
case 0: return a;
case 1: return b;
default: return c;
}
}
}
}
class Test {
static void Main() {
var a = new Foo(1, 2, 3);
for (int i = 0; i < 3; i++) {
a[i] *= 10;
Console.WriteLine("{0}", a[i]);
}
}
}
C>test
10
20
30
●LINQ
- LINQ (Language Integrated Query) は、C# のデータだけではなく、データベースの問い合わせや XML の操作などを統一して行うための仕組み
- LINQ には SQL 同様のクエリ式を使う方法と、標準的なメソッドや演算子を使う方法がある
- 前者の構文は後者のメソッドや演算子にコンパイルされる
- LINQ には便利なメソッドや高階関数が多数用意されている
- LINQ を使うときは using System.Linq;
- マッピングは Select()
- フィルターは Where()
- 畳み込みは Aggregate()
- OrderBy() は昇順に、OrderByDescending() は逆順にソートする
- 先頭からの探索が First(), 末尾からの探索が Last()
- ElementAt() は n 番目の要素を返す
- Cotains() は要素が含まれていれば真を返す
- All() は引数の関数 (述語) を要素に適用し、すべて真ならば真を返す
- Any() は引数の関数 (述語) を要素に適用し、ひとつでも真ならば真を返す
- GroupBy() はキーを指定して、キーの値が等しいもの集める (グループ化)
- その他の主なメソッド
- Take, Skip, TakeWhilte, SkipWhile
- Concat, Reverse
- Distinct, Union, Intersect, Except
- AsEnumerable, ToArray, ToList, ToDictionary
- SequenceEqual
- Range, Repeat, Empty
- Count, Sum, Min, Max, Average
csharp> var a = new int[8] {1,2,3,4,5,6,7,8};
csharp> a.Select(n => n * n);
{ 1, 4, 9, 16, 25, 36, 49, 64 }
csharp> a.Select(n => n * 1.5);
{ 1.5, 3, 4.5, 6, 7.5, 9, 10.5, 12 }
csharp> a.Where(n => n % 2 == 0);
{ 2, 4, 6, 8 }
csharp> a.Where(n => n % 2 != 0);
{ 1, 3, 5, 7 }
csharp> a.Aggregate((sum, n) => sum + n);
36
csharp> a.Aggregate(100, (sum, n) => sum + n);
136
csharp> var b = new int[] {5,6,4,7,3,8,2,9,1,0};
csharp> b.OrderBy(n => n);
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
csharp> b.OrderByDescending(n => n);
{ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }
csharp> b.First(n => n % 2 == 0);
6
csharp> b.Last(n => n % 2 != 0);
1
csharp> var c = new int[] {2,4,6,8,10};
csharp> c.All(n => n % 2 == 0);
true
csharp> c.Any(n => n % 2 != 0);
false
csharp> var d = new [] { new {name="foo", gr=1}, new {name="bar", gr=2},
> new {name="baz", gr=1}, new {name="oops", gr=2}};
csharp> d
{ { name = foo, gr = 1 }, { name = bar, gr = 2 }, { name = baz, gr = 1 }, { name = oops, gr = 2 } }
csharp> d.GroupBy(n => n.gr)s), 0 warnings
{ { { name = foo, gr = 1 }, { name = baz, gr = 1 } }, { { name = bar, gr = 2 }, { name = oops, gr = 2 } } }
- { ... } は匿名型で、コンパイラが型名を決める
- new [] { new { ... }, ... } で匿名型の配列を生成することができる
●LINQ (クエリ式)
- SQL の場合、データの抽出は select 文を使う
- select カラム名 form テーブル名 where 条件式;
- C# の場合、クエリ式を使うと次のようになる
- from 変数名 in コレクション where 条件式 select 要素;
- 昇順にソートする場合は select の前に orderby 要素 を指定する
- 逆順にソートする場合は orderby 要素 descending とする
- group 要素 by キー は指定したキーでグループ化する
- select と group の後ろにクエリ式を書くことはできない
- into でクエリ式を連結することができる
- ... into 変数 ...
- 前のクエリ式の結果が into で指定した変数に格納される
csharp> var b = new int[] {5,6,4,7,3,8,2,9,1,0};
csharp> from x in b where x % 2 == 0 select x;
{ 6, 4, 8, 2, 0 }
csharp> from x in b where x % 2 == 0 orderby x select x;
{ 0, 2, 4, 6, 8 }
csharp> var xs = new[] { new {name="foo", score=10}, new {name="bar", score=20}, new {name="baz", score=40}, new {name="oops", score=30}};
csharp> from x in xs where x.score > 20 select x.name;
{ "baz", "oops" }
csharp> from x in xs where x.score > 20 select x;
{ { name = baz, score = 40 }, { name = oops, score = 30 } }
- select で複数のフィールドを指定するときは匿名型で指定する
- この場合、クエリ式で抽出された複数の値は匿名型に格納されて返される
リスト : クエリ式の簡単な使用例
using System;
using System.Linq;
class Test {
static void Main() {
var heightTable = new [] {
new {id=1, name="Ada", height=148.7, rank=1},
new {id=2, name="Alice", height=149.5, rank=2},
new {id=3, name="Carey", height=133.7, rank=3},
new {id=4, name="Ellen", height=157.9, rank=4},
new {id=5, name="Hanna", height=154.2, rank=1},
new {id=6, name="Janet", height=147.8, rank=2},
new {id=7, name="Linda", height=154.6, rank=3},
new {id=8, name="Maria", height=159.1, rank=4},
new {id=9, name="Miranda",height=148.2, rank=1},
new {id=10, name="Sara", height=153.1, rank=2},
new {id=11, name="Tracy", height=138.2, rank=3},
new {id=12, name="Violet", height=138.7, rank=4},
};
var xs = from x in heightTable where x.height < 140
orderby x.height select new {x.name, x.height};
foreach(var x in xs) {
Console.WriteLine("{0}, {1}", x.name, x.height);
}
var ys = from x in heightTable where x.height > 150
orderby x.height descending select new {x.name, x.height};
foreach(var y in ys) {
Console.WriteLine("{0}, {1}", y.name, y.height);
}
var gs = from x in heightTable
group new { x.name, x.height } by x.rank;
foreach(var g in gs) {
Console.Write("{0}: ", g.Key); // プロパティ Key でキーの値を取得できる
foreach(var p in g) {
Console.Write("({0}, {1}) ", p.name, p.height);
}
Console.WriteLine("");
}
}
}
C>test
Carey, 133.7
Tracy, 138.2
Violet, 138.7
Maria, 159.1
Ellen, 157.9
Linda, 154.6
Hanna, 154.2
Sara, 153.1
1: (Ada, 148.7) (Hanna, 154.2) (Miranda, 148.2)
2: (Alice, 149.5) (Janet, 147.8) (Sara, 153.1)
3: (Carey, 133.7) (Linda, 154.6) (Tracy, 138.2)
4: (Ellen, 157.9) (Maria, 159.1) (Violet, 138.7)
●ファイル入出力
- テキストファイルの入出力は System.IO に定義されているクラス StreamReader, StreamWriter を使う
- テキストの読み込み
- int Read(); 1 文字 (char) 読み込み
- int Read(char[] buff, int index, int size); 配列 buff の index 番目以降に size 個の文字を読み込む
- string ReadLine(); 1 行読み込み
- string ReadToEnd(); 最後まで読み込む
- テキストの書き込み
- Write(), WriteLine() は Cosole.Write(), Console.WriteLine() と同じ
- Flush(); バッファに残っているデータを出力する
リスト : テキストファイルの連結 (cat.cs)
using System;
using System.IO;
class Test {
static void Main(string[] args) {
foreach(string name in args) {
using (var s = new StreamReader(name)) {
string buff;
while ((buff = s.ReadLine()) != null) {
Console.WriteLine(buff);
}
}
}
}
}
リスト : テキストファイルのコピー (cp.cs)
using System;
using System.IO;
class Test {
static void Main(string[] args) {
if (args.Length < 2) {
Console.WriteLine("arguments error");
} else {
using (var sIn = new StreamReader(args[0])) {
using (var sOut = new StreamWriter(args[1])) {
string buff;
while ((buff = sIn.ReadLine()) != null) {
sOut.WriteLine(buff);
}
}
}
}
}
}
- コマンドライン引数は Main() の引数 string[] args に格納される
- ReadLine() はファイルの終了を検出すると null を返す
●名前空間
- 名前空間は namespace 名前 { ... } で定義する
- 名前空間の中では複数のクラスを定義できる
- 名前空間は入れ子にすることもできる
- Name1 の中に Name2 を定義する場合、namespace Name1.Name2 { ... } と書くこともできる
csharp> namespace Foo {
> namespace Bar {
> class Baz {}
> }
> class Oops {}
> }
csharp> new Foo.Bar.Baz();
Foo.Bar.Baz
csharp> new Foo.Oops()
Foo.Oops
csharp> using Foo;
csharp> using Foo.Bar;
csharp> new Baz();
Foo.Bar.Baz
csharp> new Oops();
Foo.Oops
- 名前空間の中で定義したクラスは Name.ClassName でアクセスできる
- using Name; や using Name1.Name2; とすることで Name や Name1.Name2 を省略できる
- using で別名を付けることができる
- using 別名 = Name1.Name2.ClassName; // クラス名の別名
- using 別名 = Name1.Name2.Name3; // 名前空間の別名
- 別名を使う場合、ドット ( . ) だけではなく :: 演算子 (エイリアス修飾子) を使うことができる
- using System = Sys; Sys::Console.WriteLine("") のように使う
- 名前空間 global はルートを表す (System は global::System のこと)
●ライブラリの作成
- Windows の場合、C# のライブラリは DLL になる
- mcs のオプション -target: (または -t:) に library, -out: に DLL ファイル名を指定する
- あとはソースファイルを指定する (複数指定してもよい)
リスト : hello.cs
using System;
namespace HelloWorld {
public class Hello {
public static void Greeting() {
Console.WriteLine("Hello World");
}
}
}
C>mcs -t:library -out:hello.dll hello.cs
- DLL をリンクするときは -reference: (または -r:) で DLL ファイルを指定する
リスト : hellotest.cs
using HelloWorld;
class Test {
static void Main() {
Hello.Greeting();
}
}
C>mcs -r:hello.dll hellotest.cs
C>hellotest
Hello World
●値呼びと参照呼び
- 一般に、関数呼び出しには「値呼び (call by value)」と「参照呼び (call by reference)」がある
- 近代的なプログラミング言語では「値呼び」が主流で、C# も値呼びである
- 受け取るデータを格納する変数 (仮引数) を用意する
- データを引数に代入する (データのコピーが行われる)
- 関数の実行終了後、引数を廃棄する
- 仮引数に対する更新が直ちに実引数にも及ぶような呼び出し方が「参照呼び」
- 参照呼びは、呼び出し先 (caller) から呼び出し元 (callee) の局所変数にアクセスできる
- C# の場合、仮引数と実引数にキーワード ref を付けると参照呼びと同様の動作になる
リスト : 値の交換
using System;
class Test {
static void Swap(ref int a, ref int b) {
int c = a;
a = b;
b = c;
}
static void Main() {
int a = 1, b = 2;
Console.WriteLine("a = {0}, b = {1}", a, b);
Swap(ref a, ref b);
Console.WriteLine("a = {0}, b = {1}", a, b);
}
}
C>test
a = 1, b = 2
a = 2, b = 1
- ref を使う場合、変数は初期化しておく必要がある
- 基本的に C# の関数は一つの値しか返せない
- 複数の値 (多値) を返したい場合は参照呼びを使う
- このとき ref ではなくキーワード out を使うと変数の初期化が不要になる
リスト : out の使い方
using System;
class Test {
// 商と剰余を返す (Math.DivRem() と同じ)
static int DivRem(int x, int y, out int z) {
z = x % y;
return x / y;
}
static void Main() {
int r; // 初期化しなくてもよい
int q = DivRem(11, 4, out r);
Console.WriteLine("{0}, {1}", q, r);
}
}
C>test
2, 3
- なお、C# 7 になると「タプル (tuple)」が導入されるので、多値を返す場合はタプルを使ったほうが簡単
●データ型の判定
csharp> class Foo {};
csharp> class Bar : Foo {};
csharp> class Baz : Bar {};
csharp> Foo a = new Foo();
csharp> Foo b = new Bar();
csharp> Foo c = new Baz();
csharp> a is Foo;
true
csharp> a is Bar;
false
csharp> b is Foo;
true
csharp> b is Bar;
true
csharp> b is Baz;
false
csharp> c is Foo;
true
csharp> c is Bar;
true
csharp> c is Baz;
true
csharp> a as Foo;
Foo
csharp> a as Bar;
null
csharp> b as Bar;
Bar
csharp> b as Baz;
null
csharp> c as Baz;
Baz
- 演算子 typeof(データ型) は System.Type のインスタンスを返す
- メソッド GetType() でも System.Type のインスタンスを取得できる
- プロパティ Name でデータ型の名前 (文字列) を取得できる
csharp> typeof(Foo).Name;
"Foo"
csharp> b.GetType();
Bar
csharp> b.GetType().Name;
"Bar"
csharp> c.GetType().Name;
"Baz"
●演算子の多重定義
//
// ratio.cs : 有理数
//
// Copyright (C) 2016 Makoto Hiroi
//
using System;
using System.Numerics;
struct Ratio : IComparable {
public BigInteger Numer { get; } // 分子
public BigInteger Denom { get; } // 分母
public Ratio(BigInteger p, BigInteger q) {
BigInteger a = BigInteger.GreatestCommonDivisor(p, q);
Numer = p / a;
Denom = q / a;
if (Denom < 0) {
Numer = - Numer;
Denom = - Denom;
}
}
public static Ratio operator +(Ratio a, Ratio b) {
return new Ratio(a.Numer * b.Denom + b.Numer * a.Denom, a.Denom * b.Denom);
}
public static Ratio operator -(Ratio a, Ratio b) {
return new Ratio(a.Numer * b.Denom - b.Numer * a.Denom, a.Denom * b.Denom);
}
public static Ratio operator *(Ratio a, Ratio b) {
return new Ratio(a.Numer * b.Numer, a.Denom * b.Denom);
}
public static Ratio operator /(Ratio a, Ratio b) {
return new Ratio(a.Numer * b.Denom, a.Denom * b.Numer);
}
// IComparable 用のメソッド
public int CompareTo(Object obj) {
if (!(obj is Ratio)) return 1;
Ratio r = (Ratio)obj;
BigInteger a = Numer * r.Denom;
BigInteger b = r.Numer * Denom;
if (a == b) return 0;
else if (a < b) return -1;
else return 1;
}
public override bool Equals(object obj) {
if (!(obj is Ratio)) return false;
Ratio r = (Ratio)obj;
return Numer == r.Numer && Denom == r.Denom;
}
// とても適当なので実際に使ってはいけない
public override int GetHashCode() {
return ((Numer << 7) ^ Denom).GetHashCode();
}
public static bool operator ==(Ratio a, Ratio b) {
return a.Equals(b);
}
public static bool operator !=(Ratio a, Ratio b) {
return !a.Equals(b);
}
public static bool operator <(Ratio a, Ratio b) {
return a.CompareTo(b) < 0;
}
public static bool operator <=(Ratio a, Ratio b) {
return a.CompareTo(b) <= 0;
}
public static bool operator >(Ratio a, Ratio b) {
return a.CompareTo(b) > 0;
}
public static bool operator >=(Ratio a, Ratio b) {
return a.CompareTo(b) >= 0;
}
// 表示
public override string ToString() {
return Numer.ToString() + "/" + Denom.ToString();
}
}
class Test {
static void Main() {
Ratio a = new Ratio(1, 2);
Ratio b = new Ratio(1, 3);
Ratio c = new Ratio(1, 4);
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);
Console.WriteLine(a + b);
Console.WriteLine(a - b);
Console.WriteLine(a * b);
Console.WriteLine(a / b);
Console.WriteLine(c + b);
Console.WriteLine(c - b);
Console.WriteLine(c * b);
Console.WriteLine(c / b);
Console.WriteLine(a == c + c);
Console.WriteLine(a != c + c);
Console.WriteLine(a < b);
Console.WriteLine(a > b);
Console.WriteLine(a <= b);
Console.WriteLine(a >= b);
Console.WriteLine(b < c);
Console.WriteLine(b > c);
Console.WriteLine(b <= c);
Console.WriteLine(b >= c);
var buff = new Ratio[] {b, a, c};
Array.Sort(buff);
foreach(Ratio x in buff) {
Console.Write("{0} ", x);
}
Console.WriteLine("");
}
}
- 配列のメソッド Sort() や BinarySearch() などを使用するときは IComparable を継承する
- 実装するメソッドは x.CompareTo(y) だけ
- x < y, 負の整数 (int) を返す
- x == y, 0 を返す
- x > y, 正の整数を返す
- IComparable を継承しても比較演算子は自動的に定義されないので、自分で演算子を多重定義する必要がある
- 演算子 ==, != を多重定義するとき、メソッド Equals() をオーバーライドしないとワーニングがでる
- メソッド Equals() をオーバーライドするとき、メソッド GetHashCode() をオーバーライドしないとワーニングがでる
C>ratio
1/2
1/3
1/4
5/6
1/6
1/6
3/2
7/12
-1/12
1/12
3/4
True
False
False
True
False
True
False
True
False
True
1/4 1/3 1/2
●null 許容型
- 通常、null は値型の変数に代入することはできないが、null も代入できる値型を「null 許容型」という
- 値型を T とすると、null 許容型は T? と記述する
- T? は System.Nullable<T> の省略形
- null 許容型は通常の値型と同様の計算を行うことができる
- 計算する値に null が含まれていると結果は null になる
- null の判定は演算子 ==, != のほかにプロパティ HasValue もある
- プロパティ Value は値を返すが、null であれば例外を送出する
- null 許容型の値を値型の変数に代入するときは演算子 ?? を使うと便利
- x が null 許容型の場合、int y = x ?? -1; は x に値があればその値を、null であれば -1 を y に代入する
csharp> int? a = 10;
csharp> int? b = null;
csharp> a
10
csharp> b
null
csharp> int c = a ?? -1;
csharp> c
10
csharp> int d = b ?? -1;
csharp> d
-1
csharp> a + a;
20
csharp> a + b;
null
●日付と時刻
- C# の場合、日付と時刻の操作は主に構造体 DateTime を使う
- 現在の日付と時刻は静的なプロパティ Now で取得できる
- 現在の日付だけでよければ静的なプロパティ Today を使う
- 主なプロパティ
- Year, Month, Day, Hour, Minute, Second, Millsecond
- Date : 日付だけを取得
- TimeOfDay : 時刻だけを取得
- DayOfWeek : 曜日を取得
- Ticks : タイマー刻み数を取得
- new DateTime() で新しいインスタンスを生成できる
- 構造体 TimeSpan は時間間隔を表す
- DateTime と DateTime の引き算は TimeSpan を返す
- 主なプロパティ
- Years, Months, Days, Hours, Minutes, Seconds, Millseconds, Ticks
- これらのプロパティは TimeSpane の該当する要素の値を返すだけ
- トータルでの値は次のプロパティを使う
- TotalDays, TotalHours, TotalMinutes, TotalSeconds, TotalMillseconds
csharp> var a = new DateTime(2016, 1, 1);
csharp> var b = new DateTime(2016, 11, 6);
csharp> a
01/01/2016 00:00:00
csharp> b
11/06/2016 00:00:00
csharp> var c = b - a;
csharp> c;
310.00:00:00
csharp> c.TotalDays;
310
csharp> c.TotalHours;
7440
csharp> c.TotalSeconds;
26784000
リスト : 簡単な実行時間の計測
using System;
class Test {
// 二重再帰
static long Fibo(long n) {
if (n < 2) return n;
return Fibo(n - 2) + Fibo(n - 1);
}
static void Main() {
var s = DateTime.Now;
Console.WriteLine(Fibo(42));
Console.WriteLine((DateTime.Now - s).TotalSeconds);
}
}
C>test
267914296
3.7542147
●書式指定文字列
- メソッド ToString() はオブジェクトを文字列に変換する
- 自作のクラスで ToString() をオーバーライドすると、Console.Write() などの出力関数でオブジェクトを表示できる
- public overirde string ToString() { ... }
- データ型によっては ToSting() の引数に書式指定文字列を指定することができる
- 数値の主な書式指定子
- c. C : 通貨単位を表示
- d, D : 10 進数 (整数専用)
- x, X : 16 進数 (整数専用, x は a - f, X は A - F で表示)
- f, F : 固定小数点
- e, E : 指数
- g, G : 数値 (F, E のどちらか簡潔な方で表示)
- n, N : 桁区切り記号付き
- p, P : % 記号付き
- 詳細は MSDN : 標準の数値書式指定文字列 を参照
csharp> 255.ToString("d");
"255"
csharp> 255.ToString("x");
"ff"
csharp> 255.ToString("X");
"FF"
csharp> x.ToString("f");
"1.23"
csharp> double x = 1.23456789;
csharp> x.ToString("e");
"1.234568e+000"
csharp> x.ToString("g");
"1.23456789"
csharp> 123456789.ToString("n");
"123,456,789.00"
- 日付の主な書式指定子
- 小文字は短い形式、大文字は長い形式
- d, D : 日付
- t, T : 時刻
- f, F : 完全な日付と時刻
- g, G : 一般的な日付と時刻
- y, Y : 年月
- m, M : 月日
- 詳細は MSDN : 標準の日時書式指定文字列 を参照
csharp> a
11/06/2016 12:34:56
csharp> a.ToString("d");
"11/06/2016"
csharp> a.ToString("D");
"Sunday, 06 November 2016"
csharp> a.ToString("t");
"12:34"
csharp> a.ToString("T");
"12:34:56"
csharp> a.ToString("f");
"Sunday, 06 November 2016 12:34"
csharp> a.ToString("F");
"Sunday, 06 November 2016 12:34:56"
csharp> a.ToString("g");
"11/06/2016 12:34"
csharp> a.ToString("G");
"11/06/2016 12:34:56"
- String.Format() や Console.Write() などでは複数のデータの書式を指定することができる
- これを「復号書式指定」という
- 復号書式指定の構文
{ index[,w][:指定子] }
- index は書式文字列のあとの引数の位置
- w はフィールド幅で、負数を指定すると右詰めされる
- 指定子のあとの数字を指定することもできる
- 整数の場合はフィールド幅 (右詰めで空欄には 0 が充填)
- 浮動小数点数の場合は精度 (小数点以降の桁数)
csharp> Console.WriteLine("[{0,8}]", "hello");
[ hello]
csharp> Console.WriteLine("[{0,-8}]", "hello");
[hello ]
csharp> Console.WriteLine("[{0,-8:d}]", 123);
[123 ]
csharp> Console.WriteLine("[{0,8:d}]", 1234);
[ 1234]
csharp> Console.WriteLine("[{0,-8:d}]", 1234);
[1234 ]
csharp> Console.WriteLine("[{0:d8}]", 1234);
[00001234]
csharp> Console.WriteLine("{0:f}", 1.2345678);
1.23
csharp> Console.WriteLine("{0:f7}", 1.2345678);
1.2345678
csharp> Console.WriteLine("{0:###.###}", 123.45);
123.45
csharp> Console.WriteLine("{0:###.000}", 123.45);
123.450
csharp> Console.WriteLine("{0:0000.000}", 123.45);
0123.450