記事内に広告が含まれています

C#と比較して学ぶDart言語仕様② – 演算子・制御構文・例外処理・関数

dart_csharp_02 C#

前回の記事に続いて、Dartの言語仕様をC#と比較しながら簡単に解説していきます。

今回は、演算子・制御構文・例外処理・関数について見ていきます。

※第1回・第3回の記事はこちら

言語のバージョン

  • Dart 3.4.3
  • C# 12 (.NET 8)

演算子

まずは様々な演算子について比較していきます。

算術演算子・比較演算子・論理演算子・三項演算子

算術演算子(+, *など)・比較演算子(==, <=など)・論理演算子(&&, ||など)・三項演算子(条件式 ? 式1 : 式2)は、C#と同じなため解説は割愛します。

カスケード演算子

同じオブジェクトに対して繰り返しメソッドやプロパティを呼び出す際に便利な演算子です。

C#のメソッドチェーンに似ていますが、メソッドチェーンがメソッドの戻り値を返すのに対して、カスケード演算子はオブジェクト自体を返す点が異なります。

void main() {
    // Listオブジェクトに対するメソッドチェーン
    var list = [1, 2, 3]
      ..add(4)
      ..remove(1);

    print(list); // => [2, 3, 4]

    // Userオブジェクトに対する初期化
    var user = new User()
      ..name = 'Taro'
      ..age = 30;
}

class User {
  String? name;
  int? age;
}

C#では完全に同じように書くことはできません。

// ×下記のようなメソッドチェーンはできない(Addメソッドは戻り値が無いため)
var list = new List<int> { 1, 2, 3 }
    .Add(4)
    .Remove(1); // コンパイルエラー

// ×下記のような初期化もできない
var user = new User()
    .Name = "Test"
    .Age = 30; // コンパイルエラー

// 〇オブジェクトの初期化であればオブジェクト初期化子を使う
var user = new User
{
    Name = "Test",
    Age = 30
};

スプレッド演算子

List, Set, Mapのみで使える演算子で、コレクションを結合する際に便利です。

コレクションの前に...を付けると、そのコレクションが展開されます。

var list1 = [1, 2, 3];
var list2 = [0, ...list1];

print(list2); // [0, 1, 2, 3]

C#では従来対応するものはありませんでしたが、C# 12でスプレッド演算子が導入されました。

Dartと異なり、コレクションの前のドットは2つです。

List<int> list1 = [1, 2, 3];
List<int> list2 = [0, ..list1];

Console.Write(string.Join(", ", list2)); // => 0, 1, 2, 3

null安全

Dartもデフォルトでnull非許容型となるため、null許容型の変数を宣言する場合は、型の後ろに?を付与します。

null安全に関する演算子は複数用意されており、どれも使い方はC#と同じです。

※演算子の日本語名は全体的に統一されているわけではないようなので、C#における演算子名を使っています。

// null許容型変数を宣言(初期値を指定しない場合はnullになる)
String? str;

// null条件演算子(?の左側がnullの場合はnullを返す)
print(str?.length); // => null

// null合体演算子(??の左側がnullに場合は右側の値を返す)
print(str ?? 'nullです'); // => nullです

// null合体代入演算子(??=の左側がnullの場合は右側の値を代入する)
str ??= 'nullです';
print(str); // => nullです

// null免除演算子(アサーション演算子とも。!を付与することでnullでないことを保証する)
print(str!.length); // => 6

制御構文

次に条件分岐とループ文について解説します。

if文

Dartのif文はC#と特に違いはありません。

var num = 0;

if (num >= 1) {
    print('numは1以上です');
} else if (num <= -1) {
    print('numは-1以下です');
} else {
    print('numは0です');
}

switch文

Dartはcaseごとのbreakを省略できるといった違いはありますが、パターンマッチングが使えるなど、C#と同じような書き方ができます。

int? num = 100;

switch (num)
{
    case 20:
        print("20");
        break;
    case 40:
    case 60: // 40 || 60 でも可
        print("40 or 60");
        break;
    case >= 61 && < 100:
        print("61 ~ 99");
        break;
    case >= 100:
        print("100以上");
        break;
    case (null):
        print("null");
        break;
    default:
        print("該当なし");
        break;
}

C#ではcaseごとにbreakかreturnが必須(フォールスルーの禁止)という制約はありますが、下記の通りDartと変わらない書き方ができます。

int? num = 99;

switch (num)
{
    case 20:
        Console.WriteLine("20");
        break;
    case 40:
    case 60: // 40 or 60 でも可
        Console.WriteLine("40 or 60");
        break;
    case >= 61 and < 100:
        Console.WriteLine("61 ~ 99");
        break;
    case >= 100:
        Console.WriteLine("100以上");
        break;
    case null:
        Console.WriteLine("null");
        break;
    default:
        Console.WriteLine("該当なし");
        break;
}

switch式

どちらの言語もswitch式に対応しています。

上記のswitch文をそのままswitch式に書き直したのが下記です。

int? num = 99;

var message = switch (num) {
    20 => "20",
    40 || 60 => "40 or 60",
    >= 60 && < 100 => "61 ~ 99",
    >= 100 => "100以上",
    null => "null",
    _ => "該当なし"
};

print(message);

以下はC#のswitch式です。switchキーワードと変数名の位置がDartと逆になる点は注意です。

int? num = 99;

var message = num switch
{
    20 => "20",
    40 or 60 => "40 or 60",
    >= 60 and < 100 => "61 ~ 99",
    >= 100 => "100以上",
    null => "null",
    _ => "該当なし"
};

Console.WriteLine(message);

for文

for文はDartもC#も同じなので、Dartのみを記載します。

var list = [1, 2, 3];
 
for (int i = 0; i < list.length; i++) {
    print(list[i]);
}

for-in文、forEachメソッド

Dartのfor-in文はC#のforeach文のことです。

また、DartにはListやMapなどのコレクションが持つforEachメソッドがあり、これはC#のList<T>.ForEachメソッドに対応します。

// foreach文
for (var num in list) {
    print(num);
}

// ForEachメソッド
list.forEach(print);

C#も構文は同じです。

// foreach文
foreach (var num in list)
{
    Console.WriteLine(num);
}

// ForEachメソッド
list.ForEach(Console.WriteLine);

while文

while文やdo-while文もC#と同様ですが、実務での使用頻度はそこまで多くないと思うので詳細は割愛します。

  var i = 0;
  
  while (i < 5) {
    print(i++);
  }

その他、breakやcontinueも使い方は全く同じです。

例外処理

DartもC#もtry-catch構文があり書き方は似ていますが、DartにはError型とException型という2つの例外の型がある点が異なります。

Error型はプログラムに問題がある場合にスローされるエラーで、try-catchで捕捉せずにプログラムを終了させるべきとされています。

一方Exception型はプログラム実行時のエラーで、try-catchで捕捉して制御することが推奨されます。

例外の型を指定しないcatch句はすべてのErrorとExceptionを捕捉してしまうので、以下のようにon句とセットで使うことが多いようです。

try {
    throw Exception('例外発生');
} on Exception catch(e) {
    print(e);
    rethrow; // 例外の再スロー
}

C#で特定の例外を捕捉するにはcatch句に例外の型名を記載します。

try
{
    throw new Exception("例外発生");
}  
catch (Exception e) 
{
    Console.WriteLine(e);
    throw;
}

なお、DartにはC#同様finally句もありますが、使い方は同じなので説明は割愛します。

関数

関数も記法自体はC#とほぼ同じですが、Dartの関数はトップレベル(クラスの外側)に定義できるといった違いがあります。

void hello() {
  print('Hello!');
}

void main() {
  hello(); // => Hello!
}

=> を使って省略した書き方(アロー構文)ができる点はC#と同じです。

// 上記Hello関数の省略記法
String hello() => 'Hello!';

省略可能な引数(オプション引数)

Dartで省略可能は引数を設定する場合は、引数を[]で囲みます。

デフォルト値を設定しない場合は、省略可能な引数の型はnull許容型である必要があります。

// 省略可能な引数
void hello2([String? message]) {
  print(message);
}

// 省略可能な引数(デフォルト値あり)
void hello3([String message = 'こんにちは!']) {
  print(message);
}

void main() {
  hello2(); // => null
  hello3(); // => こんにちは!
}

C#の場合はデフォルト値を設定することで省略可能な引数になります。(省略可能にするにはデフォルト値が必須)

 // 省略可能な引数
void Hello2(string message = "こんにちは!")
{
    Console.WriteLine(message);
}

Hello2(); // => こんにちは!

名前付き引数

名前付きで引数を指定して関数を呼び出す場合は、関数の引数を{}で囲みます。

引数の前にrequiredキーワードを付けると必須にすることができます。

// 名前付き引数
void hello4({required String name, String? message = 'こんにちは!'}) {
  print('${name}さん、${message}');
}

void main() {
  hello4(name: 'Taro'); // => Taroさん、こんにちは!
}

C#では関数側で何もしなくても、呼び出し側で名前を指定して呼び出すことができます。デフォルト値が無い引数は自動で必須引数になります。

void Hello4(string name, string message = "こんにちは!")
{
    Console.WriteLine($"{name}さん、 { message}");
}

Hello4(name: "Taro"); // => Taroさん、こんにちは!

匿名関数(無名関数)

Dartでも名前の無い関数を定義することができ、それを関数オブジェクトの型であるFunction型の変数に代入して呼び出すことができます。

// 引数を2乗して返す匿名関数をFunction型の変数に代入
int Function(int) square = (x) {
  return x * x;
};

// ラムダ式(&型推論)でも書ける
int Function(int) square2 = (x) => x * x;

void main() {
  print(square(5)); // => 25
  print(square2(5)); // => 25
}

C#では同様の使い方ができます。シンプルなラムダ式がよく使われます。

// 匿名関数
var square = delegate (int x) 
{
    return x * x;
};

// ラムダ式
Func<int, int> square2 = x => x * x;

Console.WriteLine(square(5));
Console.WriteLine(square2(5));

続きの記事

おすすめ書籍

Dartの言語仕様はもちろん、Flutter開発全般について詳しい実践形式の入門書。

実務で役立つC#のイディオムやパターンが豊富に紹介されています。2024年7月に改訂版が発売されました。

C#
hiranote

コメント

タイトルとURLをコピーしました