JavaやC++経験者が戸惑うC#

JavaやC++などのオブジェクト指向言語の経験者が、C#を使い始めて「Java/C++のアレはどうやって書くんだっけ？」と思いそうなことを整理してます。
C#4.0までの内容がメインです。末尾の文献もぜひ参考にしてください。

# 演算子

## 論理演算子と条件演算子
論理演算子（|はOR、&はAND）、条件演算子（||はOR、&&はAND）はいずれも論理式を評価して、trueまたはfalseの結果を返す。
違いは、論理演算子の場合は左式・右式の両方を評価するが、条件演算子の場合は左式の評価によって結果が確定すると右式を評価しない点にある。したがって、a || bでaがtrueとなったり、a && bでaがfalseになった場合は、bは評価されない。

```csharp
int x = 0;

bool result = false & ++x == 1;
System.Console.WriteLine("result: " + result + ", x: " + x); // result: False, x: 1
result = true | ++x == 2;
System.Console.WriteLine("result: " + result + ", x: " + x); // result: True, x: 2

result = false && ++x == 1;
System.Console.WriteLine("result: " + result + ", x: " + x); // result: False, x: 2(++xは評価されない)
result = true || ++x == 1;
System.Console.WriteLine("result: " + result + ", x: " + x); // result: True, x: 2(++xは評価されない)
```

## isで互換性がある型かどうかを判定

```csharp
public class TestClass
{
  class SuperClass { }
  class SubClass : SuperClass { }

  public static void Main()
  {
    SuperClass super = new SuperClass();
    SubClass sub = new SubClass();

    System.Console.WriteLine(super is SuperClass); // True
    System.Console.WriteLine(super is SubClass);   // False

    System.Console.WriteLine(sub is SuperClass);   // True
    System.Console.WriteLine(sub is SubClass);     // True
  }
}
```

## asでキャスト失敗時に例外を発生させない

```csharp
public class TestClass
{
  class SuperClass { }
  class SubClass : SuperClass { }

  public static void Main()
  {
    SuperClass super = new SuperClass() as SuperClass;
    if (super != null) System.Console.WriteLine("SuperClass"); // SuperClass
    super = new SuperClass() as SubClass;
    if (super == null) System.Console.WriteLine("null"); // null
  }
}

```

## null許容型と関係演算子・論理演算子
null許容型にでは関係演算子・論理演算子の振る舞いに注意する必要がある。

- 関係演算子（<、>）では、一方がnullだと、評価結果はFalse
- 論理演算子では、一方がnullだと、
 - OR演算子(|)では、もう一方がTrueなら式全体の評価もTrue、Falseならnull
 - AND演算子(&)では、もう一方がFalseなら式全体の評価もFalse、Trueならnull
- NOT演算子(!)は、常にnull

```csharp
int? a = 100;
int? b = null;
System.Console.WriteLine(a < b); // False
System.Console.WriteLine(a > b); // False

bool? x = true;
bool? y = false;
bool? z = null;
System.Console.WriteLine(x | z); // True
System.Console.WriteLine(y | z); // nullのため何も表示されない
System.Console.WriteLine(x & z); // False
System.Console.WriteLine(y & z); // nullのため何も表示されない
System.Console.WriteLine(!z); // nullのため何も表示されない
```

## カンマ(,)演算子で式を逐次実行できない

```csharp
x = (y = 10, z = 20, y + z); // コンパイルエラー
x = (y = 10) + (z = 20); // こっちはOK
```

# 制御構文、例外
## switch文のフォールスルーは不可

```csharp
switch(ch)
{
  case 'A':
    System.Console.WriteLine("A");
    // break文が無いためコンパイルエラー
  case 'B':
    System.Console.WriteLine("B");
}
```

## 例外の再スローはthrow;でOK

```csharp
public class TestClass
{
  static double div(int a, int b)
  {
    try
    {
      return a / b;
    }
    catch (DivideByZeroException　e)
    {
      System.Console.WriteLine("divでcatch: " + e.Message + ", " + e.TargetSite);
      throw;
    }
  }

  public static void Main()
  {
    try
    {
      double x = div(1, 0);
    }
    catch (DivideByZeroException e)
    {
      System.Console.WriteLine("Mainでcatch: " + e.Message + ", " + e.TargetSite);
    }
  }
}
```

## checkedでオーバーフロー例外のスロー
uncheckedでスローさせないこともできる。

```csharp
public class TestClass
{
  public static void Main()
  {
    int a = 10000000, b = 10000000;
    int x;
    try
    {
      checked { x = a * b; }
      System.Console.WriteLine("例外がスローされるのでこの文は実行されない");
    }
    catch (OverflowException e)
    {
      System.Console.WriteLine(e.Message);
    }
    try
    {
      unchecked { x = a * b; }
    }
    catch (OverflowException e)
    {
      System.Console.WriteLine("例外がスローされないのでこの文は実行されない");
    }
  }
}
```

# 配列
## 配列の初期化
ブラケットで初期値を代入できる。ブラケット内は末尾がカンマでもOK。

```csharp
string[] prefectures = { "Tokyo", "Chiba", "Saitama", "Kanagawa", };

Member[] members = {
      new Member(name: "Kazumi", age: 40), // 名前付き引数指定
      new Member(name: "Nijiko", age: 40),
      new Member(name: "Makoto", age: 16),
      new Member(name: "Miyuki", age: 4),
};
```

## 多次元配列

```csharp
int[,] table = {
      {1, 2, 3, 4, 5},
      {2, 4, 6, 8, 10},
      {3, 6, 9, 12, 15},
      {4, 8, 12, 16, 20},
      {5, 10, 15, 20, 25},
};
System.Console.WriteLine(table[1, 3]); // 8
```

## 可変長引数
１つしか宣言できず、実引数リストの末尾に記述する必要がある。

```csharp
public class TestClass
{
  static void showAverage(string name, params int scores[])
  {
    int total = 0;
    for (int i = 0; i < scores.Length; i++) total += scores[i];
    System.Console.WriteLine("{0}: {1}", name, total / scores.Length);
  }

  public static void Main()
  {
    showAverage("Kazumi", 90, 100, 75, 80, 65);
  }
}
```

# クラス
## アクセス修飾子が無いメンバはprivate

```csharp
public class TestClass
{
  public class TextTestClass
  {
    string text { get; set; }
  }

  public static void Main()
  {
    var c = new TextTestClass();
    c.text = "test"; // コンパイルエラー
  }
}
```

## デストラクタを定義できる
デストラクタはガベージコレクション時（参照が無くなった時ではない）に呼び出される。アンマネージコードで確保した領域の開放などで使う。

```csharp
public class TestClass
{
  ~TestClass()
  {
    System.Console.WriteLine("GC時に呼び出される");
  }
}
```

## 別のコンストラクタの呼び出し
TestClass(string n)、TestClass(string n, int a)の順で実行される。

```csharp
public class TestClass
{
  private string name;
  private int age;
  
  public TestClass(string n)
  {
    name = n;
  }

  public TestClass(string n, int a) : this(n)
  {
    age = a;
  }
}
```

## 親クラスのコンストラクタの呼び出し

```csharp
public class SuperClass
{
  private string name;
  private int age;
  public SuperClass(string n, int a)
  {
    name = n;
    age = a;
  }
}

public class SubClass : SuperClass
{
  private string tel;
　　　　public SubClass(string n, int a, string t) : base(n, a)
  {
    tel = t;
  }
}
```

## baseで親クラスの同名フィールドへアクセス

```csharp
public class SuperClass
{
  public void Greeting()
  {
    System.Console.WriteLine("Hello!");
  }
}

public class SubClass : SuperClass
{
　　　　public void Greeting()
  {
    base.Greeting();
    System.Console.WriteLine("How are you?");
  }
}
```

## 継承させないときはsealed

```csharp
sealed public class SuperClass
{ }

public class SubClass : SuperClass // コンパイルエラー
{ }
```

## static classでシングルトンクラス

```csharp
static class SingletonClass
{
  public static string Name;
  public static void Greeting()
  {
    Console.WriteLine("Hello, " + Name);
  }

  SingletonClass() { } // コンパイルエラー（コンストラクタを定義できない）
}

public static void Main()
{
  SingletonClass.Name = "Tanaka";
  SingletonClass.Greeting(); // Hello, Tanaka

  SingletonClass c = new SingletonClass(); // コンパイルエラー（別のインスタンスを生成できない）
}
```

## virtualでメソッドのオーバライド
特にJava経験者の方は違和感を覚えるかと思いますが、子クラスで同じシグネチャのメソッドを用意しても、メソッドはオーバライド<b>されません</b>。そのため、c1.Greeting()では親クラスのメソッドが呼び出されます。
オーバライドするためには、親クラス側でオーバライドするメソッドにvirtualキーワードを付加する必要があります。子クラス側では、overrideキーワードを付与した（ただし無くてもコンパイルエラーにはならない）、同じシグネチャのメソッドを用意します。
子クラスでのvirtualメソッドのオーバライドは不要です。オーバライドしていない場合は、親クラスのメソッドが呼び出されます。

```csharp
public class SuperClass
{
  public void Greeting1()
  {
    System.Console.WriteLine("Hello!");
  }

  public virtual void Greeting2()
  {
    System.Console.WriteLine("Hello!");
  }
}

public class SubClass : SuperClass
{
  private string name;
  public SubClass(string n)
  {
    name = n;
  }
  public void Greeting1()
  {
    System.Console.WriteLine("My name is " + name + "!");
  }
  public override void Greeting2()
  {
    System.Console.WriteLine("My name is " + name + "!");
  }
}

public class TestClass
{
  public static void Main()
  {
    SuperClass c1 = new SubClass("Araiwa");
    c1.Greeting1(); // Hello!
    ((SubClass)c1).Greeting1(); // My name is Araiwa!
    c1.Greeting2(); // My name is Araiwa!
    ((SubClass)c1).Greeting2(); // My name is Araiwa!

    SuperClass c2 = new SuperClass();
    c2.Greeting1(); // Hello!
    c2.Greeting2(); // Hello!

    SubClass c3 = new SubClass("Tanaka");
    c3.Greeting1(); // My name is Tanaka!
    c3.Greeting2(); // My name is Tanaka!
  }
}
```

## readonlyフィールド
コンストラクタ内であればreadonlyフィールドも初期化可能。

```csharp
class X
{
  public readonly int x;
  public X(int i)
  {
    x = i;
  }
}

public static void Main()
{
  X c = new X(10);
  System.Console.WriteLine(c.x); // 10
  c.x = 100; // コンパイルエラー
}
```

## 匿名クラス
フィールドの名前・型・順番が一致していれば同じクラスとなる。

```csharp
var item1 = new { Id = 1, Name = "豆腐", Price = 80 };
System.Console.WriteLine("{0}: {1}({2})", item1.Id, item1.Name, item1.Price); // 1: 豆腐(80)
item1.Name = "とうふ"; // コンパイルエラー(匿名クラスのフィールドはreadonlyとなるため更新できない)

int Id = 2, Price = 100;
string Name = "かまぼこ";
var item2 = new { Id, Name, Price }; // フィールド名の指定が無い場合、変数名がそのままフィールド名となる
System.Console.WriteLine(item1.GetType().Equals(item2.GetType())); // True(item1とitem2は同じクラス)

var item3 = new { Id = 3, Name = "お麩", Value = 150 };
System.Console.WriteLine(item1.GetType().Equals(item3.GetType())); // False
var item4 = new { Id = 4, Name = "納豆", Price = 90.0 };
System.Console.WriteLine(item1.GetType().Equals(item4.GetType())); // False
var item5 = new { Id = 5, Price = 70, Name = "こんにゃく" };
System.Console.WriteLine(item1.GetType().Equals(item5.GetType())); // False
```

# プロパティ
## 自動実装プロパティ
NameとAgeはバッキングフィールド（自動実装されるプライベート変数）に値が格納される。

```csharp
public class TestClass
{
  private string id;
  public string Id
  {
    get { return id; }
    set { id = value.Length != 6 ? value : null; }
  }
  public string Name { get; set; }
  public int Age { get; private set; }
}
```

## プロパティのインターフェイスを定義・実装できる

```csharp
public interface ITestClass
{
  string Id { get; set; }
}
public class TestClass : ITestClass
{
  string id;
  public string Id
  {
    get { return id; }
    set { id = value.Length == 6 ? value : null }
  }
}
```
# 値型
## 構造体を定義できる
構造体は値型のデータ構造で、継承はできないが、インターフェイスは実装できる。

```csharp
public struct Circle
{
  public int X, Y, R;
  public Circle(int x, int y, int r)
  {
    X = x;
    Y = y;
    R = r;
  }
}
```

## 列挙型は先頭要素が0となる整数
途中で値を変更することもできる。

```csharp
enum Number { Zero, One, Two, Ten=10, Eleven, Twelve };
// 0, 1, 2, 10, 11, 12が割り当てられる
```
# デリゲート、イベント
## マルチキャストデリゲート
+=でつなげることで、AddOne()、AddTwo()の順で実行される。-=で削除することもできる。

```csharp
public class TestClass
{
  static int x;
  delegate int Add(); // デリゲートオブジェクト
  static int AddOne()
  {
    return x += 1;
  }
  static int AddTwo()
  {
    return x += 2;
  }
  public static void Main()
  {
    x = 0;
    Add add = AddOne;
    add += AddTwo;
    System.Console.WriteLine(add()); // 3
    add -= AddOne;
    System.Console.WriteLine(add()); // 5
  }
}
```

## マルチキャストイベント
elementAまたはelementBのプロパティが変更された時に、sumの値も再計算されるようにしている。

```csharp
public class TestClass
{
  delegate void ChangedEventHandler(int oldV, int newV);

  class Element
  {
    public event ChangedEventHandler Handler;
    int v;
    public int V
    {
      get { return v; }
      set
      {
        int oldV = v;
        v = value;
        if (Handler != null) Handler(oldV, v);
      }
    }
  }

  static void Main()
  {
    int sum = 0;

    var elementA = new Element();
    elementA.Handler += (oldV, newV) => sum = sum - oldV + newV;
    var elementB = new Element();
    elementB.Handler += (oldV, newV) => sum = sum - oldV + newV;

    elementA.V = 1; // 1
    System.Console.WriteLine(sum); // 1
    elementB.V = 2;
    System.Console.WriteLine(sum); // 3
  }
}
```

# ジェネリック
## 型パラメータ制約
親クラス制約・参照型制約(class)・値型制約(struct)のいずれか1つ、インタフェース制約、コンストラクタ制約(new())の順番で宣言する必要がある。

```csharp
class Test1<T> where T : Super { } // クラスSuperを継承する型Tであること
class Test2<T> where T : IEnumerable { } // インタフェースIEnumerableを実装する型Tであること
class Test3<T, V> where T : class, new() where V : struct { } // 引数なしコンストラクタを持つ参照型Tで、かつ、値型Vであること
```

## ジェネリック構造体
型パラメータの制約も可能です。

```csharp
struct GenericElement<TKey, TValue>
{
  public TKey key;
  public TValue value;
  public GenericElement (TKey k, TValue v)
  {
    key = k;
    value = v;
  }
}
```

## ジェネリックメソッド
こちらも型パラメータ制約が可能。

```csharp
public class TestClass
{
  class GreetingClass
  {
    public void Greeting<T, V>(T name, V age, T message)
    {
      System.Console.WriteLine(name + "(" + age + "): " + message);
    }
  }

  public static void Main()
  {
    var gc = new GreetingClass();
    gc.Greeting("Araiwa", 40, "Hello!");
    gc.Greeting<string, Byte>("Tanaka", 28, "Good Morning!"); // 型指定も可能
    gc.Greeting("Umeda", 25, 'Y'); // 1番目と3番目の仮引数の型が一致しないためコンパイルエラー
  }
}
```

# 名前空間
## usingによる名前空間の指定
Linqを使うにはSystem.Linqを含める必要があるが、うっかり記述を忘れて、インテリセンスが効かずに「ん？」となることがよくある。

```csharp
using System;
using System.Linq;
public class TestClass
{
  public static void Main()
  {
    Console.WriteLine("Hello!"); // Systemを省略可
    System.Console.WriteLine("World!"); // 省略しなくても良い

    string[] prefectures = { "Tokyo", "Chiba", "Saitama", "Kanagawa", };
    Console.WriteLine(prefectures.Where(p => p.EndsWith("a")).First()); // using System.LinqでLINQが使える
  }
}
```

## ::演算子で名前空間エイリアスを指定する
global::とすることで、グローバル名前空間から指定できる。
（なお、namespaceで括られない場合はglobal名前空間に定義されたことになるので注意。）

```csharp
using X = XNamespace;
namespace XNamespace
{
  public static class XClass
  {
    public static void Greeting()
    {
      System.Console.WriteLine("XNamespace.XClass: Greeting()");
    }
  }
}

namespace X
{
  public static class XClass
  {
    public static void Greeting()
    {
      System.Console.WriteLine("X.XClass: Greeting()");
    }
  }
}

public class TestClass
{
  public static void Main()
  {
    X.XClass.Greeting(); // コンパイルエラー（エイリアス名Xと名前空間名Xが衝突するため）
    X::XClass.Greeting(); // XNamespace.XClass: Greeting()
    global::X.XClass.Greeting(); // X.XClass: Greeting()
  }
}
```

## 名前空間とディレクトリ構成は無関係
作成したクラスは、デフォルトで<b>プロジェクト名.フォルダ階層</b>としてnamespaceが設定される。
しかし、別のフォルダに移したとしてもnamespaceまでは自動で変更されないので、開発者がnamespaceの記述を変える必要がある。

# 文献
[独習C# 第3版（ハーバート・シルト 翔泳社）](https://www.amazon.co.jp/%E7%8B%AC%E7%BF%92C-%E7%AC%AC3%E7%89%88-%E3%83%8F%E3%83%BC%E3%83%90%E3%83%BC%E3%83%88%E3%83%BB%E3%82%B7%E3%83%AB%E3%83%88/dp/4798122203/)
[Effective C# 4.0（ビル・ワグナー 翔泳社）](https://www.amazon.co.jp/Effective-C-4-0/dp/4798122513/)
[[完全版] 究極のC#プログラミング ~新スタイルによる実践的コーディング（川俣晶 技術評論社）](https://www.amazon.co.jp/完全版-究極のC-プログラミング-~新スタイルによる実践的コーディング/dp/4774138622/)
[プログラミングC# 第7版(Ian Griffiths オライリージャパン)](https://www.amazon.co.jp/プログラミングC-第7版-Ian-Griffiths/dp/4873116503/)