プログラミングC#読解メモ 第三章 型 前篇

今日、今年分の働き収めだった。C#書き始めるきっかけになったのもこの仕事で(今はウルトラレガシーなC++ソースコードの機能拡張をやらされてるけど。自分の中学受験の日付のコメントとかがあってなかなか感慨深い)、去年と比べるとしょぼいけど何とか賃金を受け取ってもいいくらいの働きはできてて喜びを感じる。もう一回職場でC#を書ければなおいいんだけど、どうやら次のアプリケーションフレームワークJavaが採用されそう。ちょっとしたモノ書く仕事とか回ってきたら受けるんだけどなぁ……今抱えてる大きい案件がそうできるのならそうしたい。

さて、プログラミングC#第三章の読解メモである。結構小難しいことをこちゃこちゃ書いているので自分でコード書いて検証しないとあんまり頭に残らない。コードもちょくちょく載せながら頑張ります。

constとreadonlyの違い

変更不能なメンバ変数をつくることについて、readonlyキーワードを使えばよいとあった。EffectiveC#にもconstよりreadonlyを使えとある。この二つの違いについてみてみる。

constはコンパイル時定数、readonlyは実行時定数になっている。
そのため、constにはプリミティブ型しか使えないらしい。実行時とコンパイル時の違いがあんまりまだはっきりとイメージできてないんだけど、コンパイラは僕たちが勝手に定義した型の情報なんて全く知らなくて、だからその時点では自定義型なんてものは使えないという解釈でいいのかな。
基本的にradonlyのほうが柔軟で、わからなければreadonlyにしておけばいい、みたいな感じもあるけど、constしか使えない場合もある。switchステートメントのcaseのラベルがそうらしい。試してみると確かにそうだった。コンパイル時に確定していないといけない要素はconstしか使えないらしい。その文だけ聞くと当たり前で、じゃあそのコンパイル時に確定していないといけない要素って何なんだって感じだけど。
まぁよほどのことがない限りreadonlyを使っておけばよさそう。

Nullable

nullを許容する値型のラッパー型。Nullableと書いてもいいし、int?とかわいく書いてもいい。nullをセットできるようになる。

構造体

C#における構造体とクラスの違いは参照型か値型の違いである。新しい型を定義する際にどちらを採用するかは難しい問題だ。GC的には値型のほうが後腐れなく殺せて楽らしいけど、コピーのパフォーマンスは参照型のほうが圧倒的に上だ。
「型を値型にすべき場合とは他の値型の何かと非常に本質的な一致が見られるときです」とある。要は先人の知恵に従っておきましょうということらしい。

構造体においてちょっと注意が必要なのは、比較を行う際"=="演算子とかを定義してあげないといけないということ。参照型は参照先の同一性を比較するだけでいいんだけど値型は何が等しければ等しいということになるのかを定義しないといけない。定義しないで使うとエラーになる。
==を定義したら自動的に!=が定義されそうな気もするが、そうはならない。こっちも自分で書かないといけない。

//こんな感じ
public static bool operator ==(TestStruct a, TestStruct b)
{
  return a.name == b.name;
}

public static bool operator !=(TestStruct a, TestStruct b)
{
  return a.name != b.name;
}

これだけで比較は実行できるが警告が出る。EqualsメソッドとGetHashCodeメソッドを定義しろと。
Equalsメソッドは==と同等でなくてはならない。ただし警告を見ればわかるんだけどEqualsメソッドの引数はobject型である。よって、型チェックを挟むことができる。

public override bool Equals(object o)
{
  if(o is TestStruct)
  {
    tst = (TestStruct)o;
    return this.x == tst.x
  }
  else
    return false;
}

public override int GetHashCode()
{
    return a;
}

GetHashCode()は平たく言えばidみたいなものを返す関数と思えばいいと思う。インスタンスの同一性のために使う。これを使って効率的にデータを処理するのでなければ適当でいいと書いてある。ハッシュテーブルを利用したアルゴリズムを使う際に使う値になる。確か検索とかにすごく便利なんだっけか。ちなみに当然これの帰り値の型のサイズによっては衝突があり得るわけだけど、それはもう仕方がないらしい。なお同一性の保証ということで、Equalsメソッドで等しいと判断された二つのインスタンスのGetHashCode()の帰り値は同じでないといけない。その関係で出てきた。

コンストラクタ

ちょっと変わった書き方が紹介してあった。

    public class TestClass
    {
        private int a;

        private string b;

        public TestClass()
        {
            a = 100;
        }

        public TestClass(string str)
            :this()
        {
            b = str;
        }

        public TestClass(int num, string str)
        {
            a = num;
            b = str;
        }
    }

this()で異なるコンストラクタを呼び出してそのあとに+αの処理を書くという書き方。より柔軟にコンストラクタが書けそう。またこれに使う用のコンストラクタをprivateで定義しておくなんて使い方もある。

長くなってきたので最後に静的メンバの話をして終わろう。静的コンストラクタの実行および性的メンバの初期化の順序について、面白いコードが載っていた。

   internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("プログラム開始");
            TestClass.Hoge();
            Console.WriteLine("インスタンス生成");
            var a = new TestClass();
            Console.WriteLine("インスタンス生成");
            var b = new TestClass();
            

        }
    }

    public class TestClass
    {
        public TestClass()
        {
            Console.WriteLine("インスタンスをコンストラクト");
        }

        static TestClass()
        {
            Console.WriteLine("静的なコンストラクト");
        }

        public static int s1 = SetValue("静的フィールドそのいち");

        public int ns1 = SetValue("静的でないフィールドそのいち");

        public static int s2 = SetValue("静的フィールドそのに");

        public int ns2 = SetValue("静的でないフィールドそのに");



        private static int SetValue(string msg)
        {
            Console.WriteLine(msg);
            return 1;
        }

        public static void Hoge()
        {
            Console.WriteLine("静的メソッド");
        }
    }

これにたいする出力は、

プログラム開始
静的フィールドそのいち
静的フィールドそのに
静的なコンストラクト
静的なメソッド
インスタンス生成
静的でないフィールドそのいち
静的でないフィールドそのに
インスタンスをコンストラクト
静的でないフィールドそのいち
静的でないフィールドそのに
インスタンスをコンストラクト

これは静的な要素たちがどういうタイミングで初期化、コンストラクトされるのかを知るわかりやすい例だ。なお、C#は静的初期化の開始タイミングは保証しても終了タイミングは保証しない。
今日はここまで。それにしたって読みながらコード書きながら記事書くの超だるいなぁ……もっといい方式はないだろうか。