やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

C#ツアー クラスとオブジェクト

 クラスでできることが多すぎる。学ぶのが大変。使いこなすのはもっと大変。でもこれは超大事な基本。

成果物

情報源

クラスの作成

 Pointクラスを作成する。

0/Point.cs

class Point {}

 フィールドを追加する。

class Point
{
    public int x, y;
}

 フィールドを初期化するコンストラクタを追加する。

class Point
{
    public int x, y;
    public Point(int x, int y) => (this.x, this.y) = (x, y);
}

 Pointクラスのインスタンスを生成する。

Program.cs

Point p = new Point(1, 2);

 生成されたインスタンスは参照されなくなるとGC(ガーベジ・コレクション)によって自動的に削除される。そのタイミングは指示できない。

 参照して表示すると以下。

Console.WriteLine($"{p.x}, {p.y}");
1, 2

クラスに含めることができるもの

  • 定数
  • フィールド
  • メソッド
  • プロパティ
  • インデクサ
  • イベント
  • 演算子
  • コンストラク
  • ファイナライザ

アクセス修飾子の種類

  • public
  • internal
  • protected
  • protected internal
  • private protected
  • private

型パラメータ

 ジェネリクス型。

1/Point.cs

public class Point<T>
{
    public T x, y;
    public Point(T x, T y) => (this.x, this.y) = (x, y);
}

 宣言時に型を指定する。

Program.cs

var p = new Tours.Lesson1.Point<double>(1.1d, 2.2d);
Console.WriteLine($"{p.x}, {p.y}");
1.1, 2.2

基底クラス

 継承。基底クラスは継承元クラスのこと。派生クラスは継承先クラスのこと。

public class Point3D : Point
{
    public int z;
    public Point3D(int x, int y, int z) : base(x,y) => this.z = z;
}

 base()は親のコンストラクタを呼び出す略記。

コンストラクタ() : base() { ... }

 呼び出すと以下。

var p = new Tours.Lesson2.Point3D(1,2,3);
Console.WriteLine($"{p.x}, {p.y}, {p.z}");
1, 2, 3

フィールド

3/Color.cs

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);
    private byte r, g, b;
    public Color(byte r, byte g, byte b) 
    {
        this.r = r;
        this.g = g;
        this.b = b;
    }
}
var c = new Tours.Lesson3.Color(16,32,64);
//Console.WriteLine($"{c.r}, {c.g}, {c.b}"); // error CS0122
//Tours.Lesson3.Color.White = c; // error CS0198
Console.WriteLine($"{Tours.Lesson3.Color.White}"); 
Tours.Lesson3.Color

メソッド

種別 定義例 呼出例 概要
静的メソッド static void M() {} ClassName.M(); システムに1つだけあるメソッド
インスタンスメソッド void M() {} instance.M(); インスタンス個別にあるメソッド

シグネチャ

 シグネチャはクラス内で一意である必要がある。構成要素は以下。戻り値は含まれない。

  • メソッド名
  • 型パラメータの数
  • パラメータの数、修飾子、型

 以下はOK。名前が一意であるため。

int Method1() {}
int Method2() {}

 以下もOK。引数の数が一意のため。

int Method() {}
int Method(int i) {}

 居kははNG。メソッド名、パラメータが重複している。戻り値はシグネチャに含まれないため、異なっていても一意判定に関係なし。

int Method() {}
string Method() {}

パラメータ(引数)

 パラメータには変数を渡せる。変数には型がある。型は以下の2種類に大別できる。

渡し方 概要
値型 値渡し 元の値をコピーした値が渡される
参照型 参照渡し 元の値を参照するポインタが渡される

in/ref/out

 参照型にはオプションで以下の修飾子を付与できる。これらもシグネチャに含まれる。

修飾子 呼出前の初期化 メソッド内代入
in
ref
out
public static void M_in(in Point p) { p.x++; } // p = new Point(1,2); はエラー
public static void M_ref(ref Point p) { p.x++; p = new Point(0,0); } // 既存の参照先がある。それを変更できる
public static void M_out(out Point p) { p = new Point(0,0); p.x++; } // 既存の参照先は不要。メソッド内で先に代入必須。
var p1 = new Tours.Lesson4.Point(1,1);
Console.WriteLine($"{p1.x}, {p1.y}");
Tours.Lesson4.Point.M_in(in p1);
Console.WriteLine($"{p1.x}, {p1.y}");
Tours.Lesson4.Point.M_ref(ref p1);
Console.WriteLine($"{p1.x}, {p1.y}");
Tours.Lesson4.Point.M_out(out p1);
Console.WriteLine($"{p1.x}, {p1.y}");

Tours.Lesson4.Point p2; // 初期化せず
//Tours.Lesson4.Point.M_in(in p2); // error CS0165
//Console.WriteLine($"{p1.x}, {p1.y}");
//Tours.Lesson4.Point.M_ref(ref p2); // error CS0165
//Console.WriteLine($"{p1.x}, {p1.y}");
Tours.Lesson4.Point.M_out(out p2);
Console.WriteLine($"{p1.x}, {p1.y}");
1, 1
2, 1
0, 0
1, 0
1, 0

可変長引数

 params修飾子と配列を用いる。

void M(params int[] args) {}

 コンストラクタの引数を可変長にする。N次元の点を生成する。

public class Points
{
    public int[] Values { get; private set; }
    public Points(params int[] points) => this.Values = points;
}

 4次元の点を生成する。各軸の値を取得する。

var p = new Tours.Lesson5.Points(1,2,3,4);
foreach (int x in p.Values) {
    Console.WriteLine($"{x}"); 
}

virtual, override, abstract メソッド

修飾子 和名 概要
virtual 仮想 派生クラスにてoverrideできる。しなくてもいい
override 上書き 基底クラスの実装を上書きする
abstract 抽象 抽象クラス内でのみ宣言可。派生クラスにて実装を強いる。
class V {
    public virtual void V() { Console.WriteLine("virtual"); }
}
class O : V{
    public override virtual void V() { Console.WriteLine("override"); }
}
abstract class A {
    abstract void A();
}
class B : A {
    void A () { Console.WriteLine("abstract implement."); }
}
var v = new Tours.Lesson6.V();
var o = new Tours.Lesson6.O();
//var a = new Tours.Lesson6.A(); // error CS0144
var b = new Tours.Lesson6.B();
v.M();
o.M();
b.M();
virtual
override
abstract implement.

オーバーロード

 メソッドシグネチャが一意であれば、同一名のメソッドを定義できる。

class C {
    public void M() { Console.WriteLine("M()"); }
    public void M(int i) { Console.WriteLine("M(int i)"); }
    public void M(double d) { Console.WriteLine("M(double d)"); }
}
var c = new Tours.Lesson7.C();
c.M();
c.M(0);
c.M(0d);
M()
M(int i)
M(double d)

関数メンバ

 関数メンバとは、クラス内におけるメンバのうち以下のような実行可能コードが含まれるメンバのこと。

  • コンストラク
  • ファイナライザ
  • プロパティ
  • インデクサー
  • イベント
  • 演算子
var c = new Tours.Lesson8.C();
var d = new Tours.Lesson8.C();
c.Changed += (object sender, EventArgs e) => Console.WriteLine("Changed()");
Console.WriteLine($"{c.P1}");
Console.WriteLine($"{c.P2}");
Console.WriteLine($"{c["MyKey"]}");
Console.WriteLine($"{c == c}");
Console.WriteLine($"{c == d}");
class C {
    public C() => Console.WriteLine("コンストラクタ");
    ~C() => Console.WriteLine("デストラクタ");
    public string P1 => "プロパティ1";
    public string P2 { get => "プロパティ2"; set => OnChanged(); }
    public string this[string key] { get => key; }
    public void M() => Console.WriteLine("M()");
    public event EventHandler Changed;
    public static bool operator ==(C a, C b) => Equals(a, b);
    public static bool operator !=(C a, C b) => !Equals(a, b);
    protected virtual void OnChanged() => Changed?.Invoke(this, EventArgs.Empty);
}
コンストラクタ
コンストラクタ
プロパティ1
プロパティ2
MyKey
True
False
デストラクタ
デストラクタ

 リソースの破棄はデストラクタだけでなくIDisposableを継承してDisposeを実装することでも行える。using(IDisposable d = new ?()){}内で。両方で行うようにするのが確実かつ効率的。

 C++のデストラクタというよりJavaのファイナライザに近い。

所感

 大体知ってたから流せた。でも量が多くて大変。これだけ見たらクラスって大変そうなイメージ。けど、よく使うものから順に覚えればいい。プロパティ、メソッド、コンストラクタの3つだけで大体OKだと思う。

 演算子、イベント、デストラクタ(ファイナライザ)は比較的特殊な場面でしか使わない。必要になってから覚えればいい。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux