"「C#できます」と言い放ったあいつがJavaプログラマであることを見分ける10の質問" をやってみる

実質はてなダイアリー初投稿。
「C#できます」と言い放ったあいつがJavaプログラマであることを見分ける10の質問 - 平々毎々(アーカイブ)が気になったので、やってみる。
はてな記法よくわかんない。

==演算子オーバーロードを実装してEqualsメソッドと同じ処理を実行するようにしてもよい場合はどのような時か?

  • いきなりわからない。EqualsとGetHashCodeをオーバーライドしている場合はいつでもOKじゃないのか?
  • MSDNガイドラインがあったような気がしたので、古いブクマを検索したら出てきた。

既定では、演算子 == は、2 つの参照が同じオブジェクトを示すかどうかを確認して参照の等価をテストします。したがって、この機能を取得するために参照型で演算子 == を実装する必要はありません。型が変更できない場合、つまりインスタンスに含まれているデータを変更できない場合、その型は、変更不可能なオブジェクトとして、同じ値を持つ限り同一と見なされるので、参照の等価の代わりに値の等価を比較するように演算子 == をオーバーロードするのが有効です。変更不可能な型以外で演算子 == をオーバーライドすることはお勧めしません。

Equals() と演算子 == のオーバーロードに関するガイドライン (C# プログラミング ガイド) | Microsoft Docs

なるほど。

  • 結論: immutableな型の場合。

ループ内でなければ、たとえ100個の文字列型変数であってもまとめて+演算子で連結してよい理由を説明せよ

  • コンパイラが最適化してくれたような気がする。
    • 文字列リテラルを+演算子で結合した場合、コンパイラが最適化して、結合済みの形にするのは聞いたことがある。
    • 文字列変数ではどうだったか?
    • 確かめるべし。

↓をReleaseでビルドしてildasmでILを確認。

using System;

namespace StringConcatOptimization
{
    class Program
    {
        static void Main(string[] args)
        {
            string a = "a";
            string b = "b";
            string c = "c";
            string d = "d";
            string e = "e";

            string concatinated = a + b + c + d + e;

            Console.WriteLine(concatinated);
        }
    }
}

はてなダイアリーも、さすがにMSILのシンタックスハイライトには対応していないっぽい。

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // コード サイズ       82 (0x52)
  .maxstack  3
  .locals init ([0] string a,
           [1] string b,
           [2] string c,
           [3] string d,
           [4] string e,
           [5] string concatinated,
           [6] string[] CS$0$0000)
  IL_0000:  ldstr      "a"
  IL_0005:  stloc.0
  IL_0006:  ldstr      "b"
  IL_000b:  stloc.1
  IL_000c:  ldstr      "c"
  IL_0011:  stloc.2
  IL_0012:  ldstr      "d"
  IL_0017:  stloc.3
  IL_0018:  ldstr      "e"
  IL_001d:  stloc.s    e
  IL_001f:  ldc.i4.5
  IL_0020:  newarr     [mscorlib]System.String
  IL_0025:  stloc.s    CS$0$0000
  IL_0027:  ldloc.s    CS$0$0000
  IL_0029:  ldc.i4.0
  IL_002a:  ldloc.0
  IL_002b:  stelem.ref
  IL_002c:  ldloc.s    CS$0$0000
  IL_002e:  ldc.i4.1
  IL_002f:  ldloc.1
  IL_0030:  stelem.ref
  IL_0031:  ldloc.s    CS$0$0000
  IL_0033:  ldc.i4.2
  IL_0034:  ldloc.2
  IL_0035:  stelem.ref
  IL_0036:  ldloc.s    CS$0$0000
  IL_0038:  ldc.i4.3
  IL_0039:  ldloc.3
  IL_003a:  stelem.ref
  IL_003b:  ldloc.s    CS$0$0000
  IL_003d:  ldc.i4.4
  IL_003e:  ldloc.s    e
  IL_0040:  stelem.ref
  IL_0041:  ldloc.s    CS$0$0000
  IL_0043:  call       string [mscorlib]System.String::Concat(string[])
  IL_0048:  stloc.s    concatinated
  IL_004a:  ldloc.s    concatinated
  IL_004c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0051:  ret
} // end of method Program::Main

string[]型のローカル変数が自動的に作られ、それを使用してString.Concatの呼び出しが1回にまとめられている。

  • 結論: +演算子を何回使用しても、String.Concatの呼び出し1回にまとめるように、コンパイラが最適化する。

Listのように値型を格納するジェネリックコレクションを使ってもボックス化/ボックス化解除が発生しない理由を説明せよ

  • 発生しないモノとして認識はしていたが、理由といわれても答えられない。
    • オブジェクトが(ポインタでなく)直に格納されているからと言う事でいいのか?
    • 仕様?

Full GC(Gen2 GC)が動作したときに断片化していてもコンパクションされないヒープ領域はどのような領域か?

throw; とthrow ex; の違いをスタックトレースの観点で説明せよ。

フィールドのアクセス修飾子をprivateにしプロパティのgetter/setterではそのフィールドを読み書きするだけというコードが馬鹿馬鹿しい理由を説明せよ。

  • そういう場合は、自動実装プロパティを使用すれば、簡潔に記述できる。

nullを参照している参照型変数のメソッドを呼び出そうとした場合でもNullReferenceExceptionが発生しないのは主にどういう状況か?

  • 拡張メソッドの場合?
    • 拡張メソッドを "参照変数のメソッド" と呼べるのかがよくわからない。

クラスと構造体の違いは何か?(「スタックとヒープ」以外で)

  • 変数へ代入する時にオブジェクトがコピーされる。
    • ヒープを使わずスタックを使うので軽量だが、コピーのコストに注意する必要がある。
    • ひとつの指標としては、16byteを超える場合はクラスにすべき。

デストラクタとは何か?

  • オブジェクトがGCに回収される時に呼び出されるメソッド。

インターフェースの明示的実装を利用する目的を1つ説明せよ。

  • 同じ名前と引数の型を持のメソッドを持つ、複数のインターフェイス(例: IEnumerableとIEnumerableは両方ともGetEnumerable()を持っている) を実装するため。

自信持って答えられたのは6/10。難しいです。