狐好きぷろぐらまー

狐好きプログラマーのブログです。

C#初心者がDartとC#のコードを比較してみた【その2】

こんにちは。 pregum_foxです。

今回は前回の続きです。

pregum-fox.hatenablog.jp

残りの分を書いていきたいと思います。

また、JavaやKotlinの方が詳しいという方は以下のサイトをご覧になった方が 良いかと思います。

qiita.com

項目の順番は下記のLanguage tourに則っています。

dart.dev

目次です。

動作環境

以下の動作環境で確認しました。

言語 動作環境
Dart 2.4.0
C#/.Net Core C#7.2 / .Net Core 2.1

演算子

算術演算子(2項演算子)
void main() {
  // 加算
  assert(2 + 3 == 5);

  // 減算
  assert(2 - 3 == -1);

  // 乗算
  assert(2 * 3 == 6);

  // 除算
  assert(5 / 2 == 2.5); // 戻り値はdouble
  // 除算(小数切り捨て)
  assert(5 ~/ 2 == 2); // 戻り値はint
  // 除算の余り(小数なし)
  assert((5 % 2) == 1 && (5 % 2) is int);
  // 除算の余り(小数あり)
  assert((5.5 % 2) == 1.5 && (5.5 % 2) is double);
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        // 加算
        Assert(2 + 3 == 5);

        // 減算
        Assert(2 - 3 == -1);

        // 乗算
        Assert(2 * 3 == 6);

        // 除算 (C#は割る数か割られる数を小数にしないと小数にならない。)
        Assert(5.0 / 2 == 2.5);
        // 除算(小数切り捨て)
        Assert(5 / 2 == 2);
        // 除算の余り(小数なし)
        Assert((5 % 2) == 1 && (5 % 2) is int);
        // 除算の余り(小数あり)
        Assert((5.5 % 2) == 1.5 && (5.5 % 2) is double);
    }
}
算術演算子(単項演算子)
void main() {
  int a, b;

  a = 0;
  b = ++a; // bの代入前にaをインクリメントします。
  assert(a == b); // 1 == 1

  a = 0;
  b = a++; // bの代入後にaをインクリメントします。
  assert(a != b); // 1 != 0

  a = 0;
  b = --a; // bの代入前にaをデクリメントします。
  assert(a == b); // -1 == -1

  a = 0;
  b = a--; // bの代入後にaをデクリメントします。
  assert(a != b); // -1 != 0
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        int a, b;

        a = 0;
        b = ++a; // bの代入前にaをインクリメントします。
        Assert(a == b); // 1 == 1

        a = 0;
        b = a++; // bの代入後にaをインクリメントします。
        Assert(a != b); // 1 != 0

        a = 0;
        b = --a; // bの代入前にaをデクリメントします。
        Assert(a == b); // -1 == -1

        a = 0;
        b = a--; // bの代入後にaをデクリメントします。
        Assert(a != b); // -1 != 0
    }
}
等価演算子および関係演算子
void main() {
  // 等価演算子
  assert(2 == 2);
  assert(null == null);
  assert(!(null == 0));

  var a1 = A(3);
  var a1_2 = a1;
  var a2 = A(3);
  var a3 = A(4);
  // ==演算子は
  assert(a1 == a1_2);
  // identicalで参照が等価のオブジェクトであるか比較する
  assert(identical(a1, a1_2));
  assert(a1 == a2);
  // a1とa2は値は等価だが参照は等価ではないためfalseを返る。
  assert(!identical(a1, a2));
  // a1とa3は値が等価でないためa1 != a3はtrueを返す。
  assert(a1 != a3);

  assert(2 != 3);
  assert(3 > 2);
  assert(2 < 3);
  assert(3 >= 3);
  assert(2 <= 3);
}

class A {
  int x;
  A(this.x);

  // ==演算子をオーバーライドしてxの値で比較を行うよう変更している。
  bool operator ==(o) => o is A && o.x == x;
}
using System;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        Assert(2 == 2);
        // クラスAはEquals(object obj)をオーバーライドしています。
        var a1 = new A(3);
        var a1_2 = a1;
        var a2 = new A(3);
        var a3 = new A(4);
        // a1とa1_2は同一のインスタンスなので全て同一であり等価である。
        Assert(a1 == a1_2);
        Assert(a1.Equals(a1_2));
        Assert(object.ReferenceEquals(a1, a1_2));

        // 同一のインスタンスではないのでa1 != a2はtrueを返す。
        Assert(a1 != a2);
        // メンバのxフィールドが等価なのでtrueを返す。
        Assert(a1.Equals(a2));
        // 同一のインスタンスではないので下記はtrueを返す。
        Assert(!object.ReferenceEquals(a1, a2));

        // a1とa3は同一のインスタンスではなく
        // xフィールドの値も違う為、全て同一ではなく非等価である。
        Assert(a1 != a3);
        Assert(!a1.Equals(a3));
        Assert(!object.ReferenceEquals(a1, a3));


        Assert(2 != 3);
        Assert(3 > 2);
        Assert(2 < 3);
        Assert(3 >= 3);
        Assert(2 <= 3);
    }


    public class A
    {
        public int x;
        public A(int x)
        {
            this.x = x;
        }

        public override bool Equals(object obj)
        {
            var @class = obj as A;
            return @class != null && x == @class.x;
        }

        public override int GetHashCode()
        {
            return 1249999374 + HashCode.Combine(this.x);
        }

        // もちろん==演算子をoverrideして内部でメンバ変数を比較しても上記の実装と
        // 同じように比較は可能
        //public static bool operator ==(A obj, A obj2)
        //{
        //    if (object.ReferenceEquals(obj, obj2))
        //    {
        //        return true;
        //    }
        //    if ((object)obj == null || (object)obj2 == null)
        //    {
        //        return false;
        //    }
        //    return obj.Equals(obj2);
        //}
        //public static bool operator !=(A obj, A obj2)
        //{
        //    return !(obj == obj2);
        //}
    }
}
型検査演算子
void main() {
  Object person = Person(14);
  // if文の中でPersonクラスにキャストされている。
  if (person is Person) {
    assert(person.age == 14);
  }
  (person as Person).age = 23;
  assert((person as Person).age == 23);

  Object dog = 'dog';
  // Personクラスではないため、if文の中には入らない
  if (dog is Person){
    assert(dog.age == 0);
  }
  // is!でdogがPersonクラスでないという演算子になる。
  assert(dog is! Person);
  // print((dog as Person).age == 0); 例外発生
  // as演算子でキャストを行った時点でエラー発生
  // var res = dog as Person;
}

class Person {
  int age;
  Person(this.age);
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        object person = new Person(14);

        // if文の中で変数pはPerson型として扱える。
        if (person is Person p)
        {
            Assert(p.age == 14);
        }

        var person2 = person as Person;
        person2.age = 23;
        Assert(((Person)person).age == 23);

        object dog = "dog";
        // Personクラスではないため、if文の中には入らない。
        if (dog is Person d)
        {
            Assert(d.age == 0);
        }

        // C#には is! という演算子はない為、not演算子で代用
        Assert(!(dog is Person));
        // C#ではas演算子でキャストできない場合、nullを返す。
        Assert((dog as Person)?.age == null);
    }
}

public class Person
{
    public int age;

    public Person(int age)
    {
        this.age = age;
    }
}
代入演算子

代入演算子の一覧は以下のサイトから確認できます。

dart.dev

ufcpp.net

void main() {
  num a = 5;
  assert(a == 5);

  int b;
  assert(b == null);
  b ??= 3;
  assert(b == 3);
  b ??= 7;
  assert(b == 3);

  // a op= b は a = a op bと同じ
  a += 4;
  assert(a == 9);
  a -= 3;
  assert(a == 6);
  a /= 3;
  assert(a == 2);
  a = 100;
  a %= 3;
  assert(a == 1);

  int c = 32;
  // 0b0010_0000 => 32
  c >>= 1; // シフト演算
  // 0b0001_0000 => 16
  assert(c == 16);
  c ^= 8; // 排他的論理和
  // 0b0001_1000 => 24
  assert(c == 24);
  c *= 3;
  assert(c == 72);
  c ~/= 20.5; // 小数切り捨て
  assert(c == 3);
  c <<= 5; // シフト演算
  // 2の5乗 * 3 => 96
  assert(c == 96);
  c &= 0x002f; // 論理積
  assert(c == 32);
  c |= 0x00ff; // 論理和
  assert(c == 255);

  int m = -32;
  m >>= 1; // シフト演算
  assert(m == -16);
  m = ~m; // bitごとに補数演算を行います。
  // 0xffff_ffff_ffff_fff0 => -16
  // ↓ bitごとに1/0を反転
  // 0x0000_0000_0000_000f => 15
  assert(m == 15);
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        int a = 5;
        Assert(a == 5);

        // int?でnullを代入できるようにする。
        int? b = null;
        Assert(b == null);
        // null合体代入演算子(??=)はC#にはないため??で代用する。 
        // ??= はC#8.0で追加される予定。
        b = b ?? 3;
        Assert(b == 3);
        b = b ?? 7;
        Assert(b == 3);

        a += 4;
        Assert(a == 9);
        a -= 3;
        Assert(a == 6);
        a /= 3;
        Assert(a == 2);
        a = 100;
        a %= 3;
        Assert(a == 1);

        int c = 32;
        // 0b0010_0000 => 32
        c >>= 1;
        Assert(c == 16);
        c ^= 8;
        Assert(c == 24);
        c *= 3;
        Assert(c == 72);
        // c ~/= はC#ではないため /= で代用
        c /= (int)20.5;
        Assert(c == 3);
        c <<= 5; // シフト演算
        Assert(c == 96);
        c &= 0x002f; // 論理積
        Assert(c == 32);
        c |= 0x00ff; // 論理和
        Assert(c == 255);

        int m = -32;
        m >>= 1; // シフト演算
        Assert(m == -16);
        m = ~m; // bitごとに補数演算を行います。
        // 0xffff_ffff_ffff_fff0 => -16
        // ↓ bitごとに1/0を反転
        // 0x0000_0000_0000_000f => 15
        Assert(m == 15);
    }
}
論理演算子
void main() {
  var True = true;
  var False = false;

  assert(!False); // 否定
  assert(True && !False); // 論理積
  assert(True || False); // 論理和

  // echoFalse()のみ呼ばれる。
  print('短絡評価テスト1');
  assert(!(echoFalse() && echoTrue()));
  // echoTrue()のみ呼ばれる。
  print('短絡評価テスト2');
  assert(echoTrue() || !echoFalse());
  // echoFalse()、echoTrue()両方呼ばれる。
  print('短絡評価テスト3');
  assert(!(echoFalse() & echoTrue()));
  // echoTrue()、echoFalse()両方呼ばれる。
  print('短絡評価テスト4');
  assert(echoTrue() | !echoFalse());
}

// 実行結果
// 短絡評価テスト1
// false
// 短絡評価テスト2
// true
// 短絡評価テスト3
// false
// true
// 短絡評価テスト4
// true
// false

bool echoTrue()
{
  print("true");
  return true;
}

bool echoFalse()
{
  print("false");
  return false;
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        // @を先頭につけることでキーワードを
        // 変数名として使用可能。
        var @true = true;
        var @false = false;

        Assert(!@false); // 否定
        Assert(@true && !@false); // 論理積
        Assert(@true || @false); // 論理和

        // EchoFalse()のみ呼ばれる。
        WriteLine("短絡評価テスト1");
        Assert(!(EchoFalse() && EchoTrue()));
        // EchoTrue()のみ呼ばれる。
        WriteLine("短絡評価テスト2");
        Assert(EchoTrue() || !EchoFalse());
        // EchoFalse()、EchoTrue()両方呼ばれる。
        WriteLine("短絡評価テスト3");
        Assert(!(EchoFalse() & EchoTrue()));
        // EchoTrue()、EchoFalse()両方呼ばれる。
        WriteLine("短絡評価テスト4");
        Assert(EchoTrue() | !EchoFalse());
    }

    // 実行結果
    // 短絡評価テスト1
    // false
    // 短絡評価テスト2
    // true
    // 短絡評価テスト3
    // false
    // true
    // 短絡評価テスト4
    // true
    // false

    public static bool EchoTrue()
    {
        WriteLine("true");
        return true;
    }

    public static bool EchoFalse()
    {
        WriteLine("false");
        return false;
    }
}
シフト演算子
void main() {
  var value = 0x22;
  assert((value << 4) == 0x220);
  assert((value >> 4) == 0x02);
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var value = 0x22;
        Assert((value << 4) == 0x220);
        Assert((value >> 4) == 0x02);
    }
}
Dartのみ】カスケード表記(..) / スプレッド演算子( ... / ...? )

スプレッド演算子はList、Map、Setリテラルに使用できます。

github.com

void main() {
  // カスケード演算子
  // Flutterの方でWidgetのプロパティ設定用に使うと
  // 短くかけて、幸せになれるらしい。
  var lis = <int>[1, 2, 3]
    ..add(4)
    ..add(5);

  // List<E>.generate(~)は、C#でいうEnumerable.Range(~)みたいなもの
  var lis2 = <num>[6.6, 7.7, 8.8]
    ..addAll(List<num>.generate(3, (i) => (i + 3) * 1.1 + 6.6));
  
  var nullLis;

  var lis3 = [
    ...?nullLis,
    999,
    ...lis2,
  ];

  assert(lis.length == 5);
  assert(lis3.length == 7);
  assert(lis2 is List<num>);
  print(lis3.runtimeType); // => List<dynamic>
  assert(lis is List<int>);
}
その他の演算子

上記以外には

  • () ・・・ 関数を呼び出す
  • [] ・・・ リスト内の指定されたインデックスの値を参照
  • . ・・・ 式のメンバを参照します。(foo.bar はfoo内のbarメンバを意味します。)
  • ?. ・・・ 右辺値が式がnullだった場合、メンバを呼び出さずnullを返します。( foo?.barでfooがnullの場合、左辺値はnullになります。)
void main() {
  var lis = <int>[1, 2, 3];

  // add()で()演算子が使用されている。
  // ついでに.演算子でList<int>のaddメソッドが呼ばれている。
  lis.add(4);

  // 下記のlis[2]で[]演算子を使用している。
  assert(lis[3] == 4);

  List<int> nullList;
  // ?が付いていない呼び出しだとExceptionが発生する。
  // ?.でnullListがnullなので何も起こらず次の処理へ移る。
  nullList?.add(3);
  assert(nullList == null);
}
using System.Collections.Generic;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var lis = new List<int> { 1, 2, 3 };
        lis.Add(4);
        Assert(lis[3] == 4);

        List<int> nullList = null;
        nullList?.Add(3);
        Assert(nullList == null);
    }
}

制御構文

if 文
void main() {
  var a = 3;
  if (a == 3) {
    print('process "if" path.');
  } else if (a >= 2) {
    print('process "else-if" path.');
  } else {
    print('process "else" path.');
  }

  var b = 2;
  if (b == 3) {
    print('process "if" path.');
  } else if (b >= 2) {
    print('process "else-if" path.');
  } else {
    print('process "else" path.');
  }

  var c = 1;
  if (c == 3) {
    print('process "if" path.');
  } else if (c >= 2) {
    print('process "else-if" path.');
  } else {
    print('process "else" path.');
  }
}
// 実行結果
// process "if" path.
// process "else-if" path.
// process "else" path.
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var a = 3;
        if (a == 3)
        {
            WriteLine(@"process ""if"" path.");
        }
        else if (a >= 2)
        {
            WriteLine(@"process ""else-if"" path.");
        }
        else
        {
            WriteLine(@"process ""else"" path.");
        }

        var b = 2;
        if (b == 3)
        {
            WriteLine(@"process ""if"" path.");
        }
        else if (b >= 2)
        {
            WriteLine(@"process ""else-if"" path.");
        }
        else
        {
            WriteLine(@"process ""else"" path.");
        }

        var c = 1;
        if (c == 3)
        {
            WriteLine(@"process ""if"" path.");
        }
        else if (c >= 2)
        {
            WriteLine(@"process ""else-if"" path.");
        }
        else
        {
            WriteLine(@"process ""else"" path.");
        }
    }
}
// 実行結果
// process "if" path.
// process "else-if" path.
// process "else" path.
for 文
void main() {
  var message = StringBuffer('Dart is fun');
  for (var i = 0; i < 5; i++) {
    message.write('!');
  }
  print(message);

  print('キャプチャテスト');
  var callbacks = [];
  for (var i = 0; i < 2; i++) {
    // クロージャごとに値を保持している。
    callbacks.add(() => print(i));
  }
  callbacks.forEach((c) => c());

  // for-inでC#のforeachのように反復処理が行うことができる。
  print('for-inテスト');
  var collection = [0, 1, 2];
  for (var x in collection) {
    print(x);
  }
}
// 実行結果
// Dart is fun!!!!!
// キャプチャテスト
// 0
// 1
// for-inテスト
// 0
// 1
// 2
using System;
using System.Collections.Generic;
using System.Text;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var message = new StringBuilder("C# is fun");
        for (int i = 0; i < 5; i++)
        {
            message.Append("!");
        }
        WriteLine(message);

        WriteLine("キャプチャテスト");
        var callbacks = new List<Action>();
        for (int i = 0; i < 2; i++)
        {
            // C#の場合はラムダ式内の変数はループ内で同じ変数扱いでキャプチャされるので
            // Dartと同じ動作にする場合、一度ローカルに代入するか
            // 別メソッドを作成する必要がある。
            var t = i;
            callbacks.Add(() => WriteLine(t));
        }
        callbacks.ForEach((c) => c());

        // C#はforeachで反復処理を行う。
        WriteLine("for-eachテスト");
        var collection = new List<int> { 0, 1, 2 };
        foreach (var x in collection)
        {
            WriteLine(x);
        }
    }
}
// 実行結果
// C# is fun!!!!!
// キャプチャテスト
// 0
// 1
// for-eachテスト
// 0
// 1
// 2
while 文
do-while 文
break文 / continue文
void main() {
  print('while文');
  // while文
  var list = [];
  while (list.length < 5) {
    list.add(list.length);
    print(list);
  }
  print('do-while文');
  // do-while文
  list.clear();
  do {
    list.add(list.length);
    print(list);
  } while (list.length < 5);
  print('break文');
  // break文
  while (true) {
    list.removeLast();
    if (list.isEmpty) break;
    print(list);
  }
  print('continue文');
  // continue文
  list.clear();
  while (list.length < 10) {
    list.add(list.length);
    if (list.length % 2 == 0) continue;
    print(list);
  }
}
// 実行結果
// [0, 1]
// [0, 1, 2]
// [0, 1, 2, 3]
// [0, 1, 2, 3, 4]
// do-while文
// [0]
// [0, 1]
// [0, 1, 2]
// [0, 1, 2, 3]
// [0, 1, 2, 3, 4]
// break文
// [0, 1, 2, 3]
// [0, 1, 2]
// [0, 1]
// [0]
// continue文
// [0]
// [0, 1, 2]
// [0, 1, 2, 3, 4]
// [0, 1, 2, 3, 4, 5, 6]
// [0, 1, 2, 3, 4, 5, 6, 7, 8]
using System.Collections.Generic;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        WriteLine("while文");
        var list = new List<int>();
        while (list.Count < 5)
        {
            list.Add(list.Count);
            WriteLine($"[{string.Join(", ", list)}]");
        }

        WriteLine("do-while文");
        list.Clear();
        do
        {
            list.Add(list.Count);
            WriteLine($"[{string.Join(", ", list)}]");
        } while (list.Count < 5);

        WriteLine("break文");
        while (true)
        {
            list.RemoveAt(list.Count - 1);
            if (list.Count == 0) break;
            WriteLine($"[{string.Join(", ", list)}]");
        }

        WriteLine("cointinue文");
        list.Clear();
        while (list.Count < 10)
        {
            list.Add(list.Count);
            if (list.Count % 2 == 0) continue;
            WriteLine($"[{string.Join(", ", list)}]");
        }
    }
}
// 実行結果
// while文
// [0]
// [0, 1]
// [0, 1, 2]
// [0, 1, 2, 3]
// [0, 1, 2, 3, 4]
// do-while文
// [0]
// [0, 1]
// [0, 1, 2]
// [0, 1, 2, 3]
// [0, 1, 2, 3, 4]
// break文
// [0, 1, 2, 3]
// [0, 1, 2]
// [0, 1]
// [0]
// cointinue文
// [0]
// [0, 1, 2]
// [0, 1, 2, 3, 4]
// [0, 1, 2, 3, 4, 5, 6]
// [0, 1, 2, 3, 4, 5, 6, 7, 8]
switch文
void main() {
  // break文かreturn文が各caseの最後にないと
  // エラーが発生します。
  Command command = Command.Open;
  switch (command) {
    case Command.Open:
      print('open');
      break;
    case Command.Close:
      print('close');
      break;
    case Command.Lock:
      print('lock');
      break;
    case Command.UnLock:
      print('unlock');
      break;
    case Command.None:
    default:
      print('none or default');
      break;
  }

  print('フォールスルーテスト');
  var none = Command.None;
  switchSample(none);
}

void switchSample(Command cmd) {
  switch (cmd) {
    case Command.None:
      print('execute none.');
      // continue文とラベルを使用することで
      // フォールスルーを行うことができる。
      continue fallThrough;

    fallThrough:
    case Command.Open:
      print('execute open.');
      return;
    case Command.Close:
      print('execute close.');
      return;
    case Command.Lock:
      print('execute lock');
      return;
    case Command.UnLock:
      print('execute unlock');
      return;
  }
}
enum Command {
  None,
  Open,
  Close,
  Lock,
  UnLock,
}
// 実行結果
// open
// フォールスルーテスト
// execute none.
// execute open.
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        Command command = Command.Open;
        switch (command)
        {
            case Command.Open:
                WriteLine("open");
                break;
            case Command.Close:
                WriteLine("close");
                break;
            case Command.Lock:
                WriteLine("lock");
                break;
            case Command.UnLock:
                WriteLine("unlock");
                break;
            case Command.None:
            default:
                WriteLine("none or default");
                break;
        }

        WriteLine("フォールスルーテスト");
        var none = Command.None;
        SwitchSample(none);
    }

    static void SwitchSample(Command cmd)
    {
        switch (cmd)
        {
            // C#では処理後のフォールスルーができない。
            case Command.None:
                WriteLine("execute none.");
                SwitchSample(Command.Open);
                break;
            case Command.Open:
                WriteLine("execute open.");
                return;
            case Command.Close:
                WriteLine("execute close.");
                return;
            case Command.Lock:
                return;
            case Command.UnLock:
                return;
            default:
                return;
        }
    }
}
enum Command
{
    None,
    Open,
    Close,
    Lock,
    UnLock,
}
// 実行結果
// open
// フォールスルーテスト
// execute none.
// execute open.

Assert

Assert(assert)メソッドは引数がtrueにならないと例外を発生させる メソッドです。

このメソッドはC#だとDebugビルドの時("DEBUG"シンボル設定時) のみ実行されます。 Dartでは、--enable-assertsオプションが指定された時実行されます。

void main() {

  // assert()メソッド
  var text;
  assert(text == null);

  // 第1引数がfalseになった場合、第2引数に設定したメッセージを表示します。
  // 第2引数に設定しなかった場合、デフォルトのメッセージが表示されます。
  var number = 100;
  assert(number < 100, 'numberは100未満で設定する必要があります。');
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {

        // Assert()メソッド
        string text = null;
        Assert(text == null);

        // 第1引数がfalseになった場合、第2引数に設定したメッセージを表示します。
        // 第2引数に設定しなかった場合、デフォルトのメッセージが表示されます。
        var number = 100;
        Assert(number < 100, "numberは100未満で設定する必要があります。");
    }
}

例外処理

void main() {
  try {
    throwException();
  } catch (e, s) {
    // 再throwされないとここではキャッチしない。
    // assert(e is OutOfMemoryError);
    // print(s);
  }
}

void throwException() {
  print('throw ex');

  // 任意の型をthrowすることが可能
  // 基本的にはError型またはException型を実装する型をthrowします。
  try {
    throw FormatException();
  }
  // on ~ catchで任意の型のみ通す
  on UnimplementedError catch (e) {
    print('catch unimplemented error.');
    print(e);
    // rethrowで再throwが可能
    rethrow;
  }
  // catch句は複数指定可能
  on Exception catch (e, s) {
    print('catch exception');
    print(e);
    print(s);
  } on Error catch (e, s) {
    print('catch error');
    print(e);
    // 型がわからない場合はcatchですべての例外をキャッチ可能
    // 第1引数は例外の変数で、第2引数はスタックトレースを取得可能
  } catch (e, s) {
    print('catch unknown exception');
    print(e);
    print(s);
  } finally {
    // 例外発生関係なく必ず通る
    print('finally');
  }
}
// 実行結果
// throw ex
// catch exception
// FormatException
// #0      throwException    bin\main.dart:17
// #1      main              bin\main.dart:3
// #2      _startIsolate.<anonymous closure>  (dart:isolate-patch/isolate_patch.dart:301:19)
// #3      _RawReceivePortImpl._handleMessage  (dart:isolate-patch/isolate_patch.dart:172:12)
// finally
using System;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        try
        {
            ThrowException();
        }
        catch (Exception e)
        {
            Assert(e is FormatException);
            WriteLine(e);
        }
    }

    static void ThrowException()
    {
        WriteLine("throw ex");

        // Exception型を実装する型をthrowします。
        try
        {
            throw new FormatException();
        }
        catch (NotImplementedException e)
        {
            WriteLine("catch notimplemented exception.");
            WriteLine(e.ToString());
            // C#の場合はthrowで再throwが可能。
            // throw e; とすると、スタックトレースが上書きされる為注意
            throw;
        }
        // catch句は複数指定可能
        catch (Exception e)
        {
            // e.ToString(); でエラーメッセージとスタックトレースを出力することができる。
            // エラーメッセージ : e.Message;
            // スタックトレース : e.StackTrace;
            // e.ToString() == e.Message + e.StackTrace;
            WriteLine("catch exception");
            WriteLine(e.ToString());
        }
        finally
        {
            WriteLine("finally");
        }
    }
}
// 実行結果
// throw ex
// 例外がスローされました: 'System.FormatException' (ConsoleApp3.dll の中)
// catch exception
// System.FormatException: One of the identified items was in an invalid format.
//    at Program.ThrowException() in C:\Users\XXX\Source\repos\ConsoleApp3\ConsoleApp3\Program.cs:line 25
// finally

クラス

コンストラク
import 'dart:math';

void main() {
  var p = Point(2, 2);

  // p.yメンバはfinalが指定されている為、再代入不可
  // p.y = 3  // <= error!

  // コンパイル時定数を作成した場合、単一のインスタンスが生成される。
  var constP = const Point(3, 3);
  final constP2 = const Point(3, 3);
  assert(identical(constP, constP2));

  // constをつけるとコンパイル時定数になり
  // new(つけなくてもよい)をつけると非定数になる。
  var a = const Point(4, 4);
  var b = new Point(4, 4);
  assert(!identical(a, b)); // aとbの参照は非等価である。

  // 名前付きコンストラクタ
  var samp1 = const Sample.anonymous(4, 5);
  var samp2 = Sample.anonymous(4, 5);
  assert(samp1.first == 4);
  assert(!identical(samp1, samp2));
}

class Sample {
  final first;
  final second;
  // コンストラクタ(thisをつけることで代入式を省略可能)
  // firstとsecondフィールドへの代入処理と同じ処理を行っている。
  Sample(this.first, this.second);
  // 上記の処理は以下のコードと同じ
  // Sample(int f, int s) {
  //   this.first = f;
  //   this.seconf = s;
  // }

  // 名前付きコンストラクタ(オーバーロードの代わり)
  // constをつけることでコンパイル時定数
  const Sample.anonymous(int first, int second)
      : this.first = first,
        this.second = second;

  @override
  String toString() {
    return 'first: $first, second: $second';
  }
}
using System;
using System.Drawing;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var p = new Point(2, 2);
        p.Y = 3;

        // C#にはコンパイル時定数のインスタンスはない為
        // とばします。

        // C#ではnewは省略不可能
        var samp1 = new Sample(4, 5);
        var samp2 = new Sample(4, 5);
        Assert(samp1.first == 4);
        Assert(!object.ReferenceEquals(samp1, samp2));
    }
}

public class Sample
{
    // フィールド版
    public readonly int first;
    public readonly int second;
    // プロパティ版
    //public int First { get;} 
    // public int Second { get; }

    public Sample(int first, int second)
    {
        this.first = first;
        this.second = second;
    }

    // コンストラクタのオーバーロード
    public Sample(string hoge)
    {
        throw new NotImplementedException();
    }

    public override string ToString()
    {
        return $"first: {first}, second: ${second}";
    }
}
コンストラクタ継承
void main() {
  var emp = Employee.fromJson();
  // 1つ以上コンストラクタが定義されていた場合、
  // デフォルトコンストラクタ(引数なし)は使用できない。
  // var emp2 = Employee(); // <= error!

  print('cast');
  if (emp is Person) {
    emp.firstName = 'Bob';
  }
  (emp as Person).firstName = 'Bob';
  print(emp.firstName);
}
// 実行結果
// in Person
// in Employee
// cast
// Bob

class Person {
  String firstName;

  Person.fromJson() {
    print('in Person');
  }
}

class Employee extends Person {
  // 明示的に親クラスのコンストラクタを呼ぶ必要がある。
  Employee.fromJson() : super.fromJson() {
    print('in Employee');
  }
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var emp = new Employee(3);

        WriteLine("cast");
        if (emp is Person e)
        {
            e.FirstName = "Bob";
        }
        (emp as Person).FirstName = "Bob";
        WriteLine(emp.FirstName);
    }
}
// 実行結果
// in Person with firstName
// in Employee with base
// in Employee with this
// cast
// Bob

public class Person
{
    public string FirstName { get; set; }

    public Person()
    {
        WriteLine("in Person");
    } 

    public Person(string firstName)
    {
        this.FirstName = firstName;
        WriteLine("in Person with firstName");
    }
}

public class Employee : Person
{
    // 下記の場合だとbaseをつけなくても良いが
    // 明示的に引数を渡したい場合はbase初期化子をつける必要がある。
    public Employee() // : base()を付けた場合と同じ
    {
        WriteLine("in Employee");
    }

    // 親クラスに明示的に引数を渡す場合の構文
    public Employee(string firstName) : base(firstName)
    {
        WriteLine("in Employee with base");
    }

    // thisをつけることで別のコンストラクタを呼ぶことができる。
    public Employee(int x) : this(x.ToString())
    {
        WriteLine("in Employee with this");
    }
}
初期化子リスト
void main() {
  var samp = Sample(17, 170);

  assert(samp.age == 17);
  assert(samp.tall == 170);
}
// 実行結果
// 17
// 170

class Sample {
  final int age;
  int tall;
  // finalが指定されているメンバを初期化する場合は
  // 初期化子リストを使用して初期化を行う。
  Sample(int age, int tall)
      : assert(age != null),
        this.age = age {
    print(age);
    // final指定でないものはbody内に記述可能
    this.tall = tall;
    print(tall);
  }

  // 以下の書き方だとコンパイルエラーが発生し実行できない。
  // Sample(int age)
  // {
  //   this.age = age;
  // }


}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var samp = new Sample(17, 170);

        Assert(samp.age == 17);
        Assert(samp.tall == 170);
    }
}
// 実行結果
// 17
// 170

class Sample
{
    public readonly int age;
    public int tall;

    public Sample(int age, int tall)
    {
        // C#はbody内に記述可能。
        this.age = age;
        WriteLine(age);
        this.tall = tall;
        WriteLine(tall);
    }
}
Dartのみ】不変コンストラクタ / ファクトリコンストラク
void main() {
  print('不変コンストラクタテスト');
  final samp = Sample(17, 170);
  const samp2 = Sample(17, 170);
  const samp3 = Sample(17, 170);
  assert(!identical(samp, samp2));
  assert(identical(samp2, samp3));
  // 不変コンストラクタなのでメンバの値は変更不可
  // samp3.age = 3; // error!

  print('ファクトリコンストラクタテスト');
  var table1 = TableSample('test1');
  var table2 = TableSample('test2');
  var table3 = TableSample('test1');
  assert(identical(table1, table3));
  assert(!identical(table1, table2));
}
// 実行結果
// 不変コンストラクタテスト
// ファクトリコンストラクタテスト
// initialize test1
// initialize test2
// test1 contains table

class Sample {
  final int age;
  final int tall;

  // 不変コンストラクタ
  const Sample(this.age, this.tall);
}

class TableSample{
  static final Map<String, TableSample> _table = <String, TableSample>{};
  final String key;

  // ファクトリコンストラクタ
  // ファクトリコンストラクタ内ではthisは使用不可
  factory TableSample(String key){
    if(_table.containsKey(key)){
      print('$key contains table');
      return _table[key];
    } else {
      print('initialize $key');
      final tableSample = TableSample._internal(key);
      _table[key] = tableSample;
      return tableSample;
    }
  }

  TableSample._internal(this.key);
}
ゲッタ/セッタ
void main() {
  var rect = Rectangle(3, 4, 20, 15);
  assert(rect.left == 3);
  rect.right = 12;
  assert(rect.left == -8);
}

class Rectangle {
  int left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // ゲッタ
  int get right => left + width;
  // セッタ
  set right(int value) => left = value - width;
  // ゲッタ
  int get bottom => top + height;
  // セッタ
  set bottom(int value) => top = value - height;
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var rect = new Rectangle(3, 4, 20, 15);
        Assert(rect.left == 3);
        rect.Right = 12;
        Assert(rect.left == -8);
    }
}

public class Rectangle
{
    public int left, top, width, height;
    public Rectangle(int left, int top, int width, int height)
    {
        this.left = left;
        this.top = top;
        this.width = width;
        this.height = height;
    }
    public int Right
    {
        get => left + width;            // ゲッタ
        set => left = value - width;    // セッタ
    }
    public int Bottom
    {
        get => top + height;            // ゲッタ
        set => top = value - height;    // セッタ
    }
}

途中ですが

又途中ですが、長くなってきましたのでここで一旦区切りたいと思います。

25000文字程度にする予定でしたが、気づいたら30000文字を超えていました。。。

残っている大まかな項目は以下の通りです。(細かい部分は変わるかもしれません。)

  • 列挙型
  • ミックスイン
  • クラス変数とクラスメソッド
  • 【C のみ】拡張メソッド
  • ジェネリクス
  • ライブラリと可視性
  • ライブラリの使用
  • ライブラリの実装
  • 非同期処理
  • ジェネレータ
  • 呼び出し可能クラス
  • Isolate
  • Typedefs
  • メタデータ
  • コメント

雑感

やっとクラスの説明がほとんど終わって、終わりが見え始めては来ていますが、

細かい項目が意外とあるので、もしかしたらあと2part必要になるかもしれません。

おそらくこのあたりから、私の理解が足りない部分が沢山出てくるかと思いますので、

指摘してもらえると助かります。

読んで頂き、ありがとうございました。

参考サイト