狐好きぷろぐらまー

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

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

こんにちは。 pregum_foxです。

今更ですが、youtube見るのにvimiumが便利すぎて、感動しています。

vimを使われている方でvimiumを使用されていない方は 是非vimiumで検索してみてください。

幸せになれると思います。

さて前回の続きから書いていきたいと思います。

前回の記事は下記からご覧下さい。

比較する項目の順番は下記のDartのlanguage tourのサイトの項目の順番に則って書いていきます。

dart.dev

また、Dart側に合わせてC#側を書いているので、Dart側にしかない言語機能などは、C#では比較しない方針です。

ご了承ください。

それでは目次です。

動作環境

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

クラス

抽象クラス / 抽象メソッド
派生クラス
void main() {
  var container = ConcreteContainer();
  container.updateChildren();
  assert(container._name == 'concrete');
}

// abstractをつけることで抽象クラスになります。
abstract class AbstractContainer {

  String _name;
  void updateChildren(); // これで抽象メソッドになっています。

  // これは具象メソッドです。
  void concreteMethod(){
    print('concrete method.');
  }
}
// 実行結果
// updateChildren
// concrete method.

class ConcreteContainer extends AbstractContainer{

  // 抽象メソッドを具象化します。
  // overrideアノテーションはなくても問題ありませんが、
  // あった方が継承したことがわかるのでつけておいた方がいいと思います。
  @override
  void updateChildren() {
    print('updateChildren');
    // 継承元のメンバも触れます。
    _name = 'concrete';
    this.concreteMethod();
  }

  @override
  void concreteMethod() {
    super.concreteMethod();
  }
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        ConcreteContainer concrete = new ConcreteContainer();
        AbstractContainer abstConcrete = new ConcreteContainer();
        WriteLine("concrete -----");
        concrete.UpdateChildren();
        concrete.VirtualMethod();
        Assert(concrete._name == "concrete");

        WriteLine("abst concrete -----");
        abstConcrete.UpdateChildren();
        abstConcrete.VirtualMethod();
    }
}
// 実行結果
// concrete -----
// derived children
// concrete virtual method.
// abst concrete -----
// derived children
// virtual method.

// abstractキーワードをつけることで抽象クラスになります。
abstract class AbstractContainer
{
    // protectedアクセス修飾子をつけることで、で基底クラスとその派生クラス内でしか
    // アクセスできないメンバになります。
    //protected string _name;
    public string _name;

    // abstractキーワードをつけることで抽象メソッドになります。
    public abstract void UpdateChildren();

    // 仮想メソッドです。
    // overrideで再定義することができます。
    public virtual void VirtualMethod()
    {
        WriteLine("virtual method.");
    }

    // これは具象メソッドです。
    void ConcreteMethod()
    {
        WriteLine("concrete method.");
    }
}

class ConcreteContainer : AbstractContainer
{
    // overrideキーワードをつけることで抽象メソッドを具象化します。
    // overrideキーワードの代わりにをnewにすると上書きを行います。
    public override void UpdateChildren()
    {
        WriteLine("derived children");
        _name = "concrete";
    }

    // 上書きをしています。
    public new void VirtualMethod()
    {
        WriteLine("concrete virtual method.");
    }

}
暗黙的なインターフェース

Dartは全てのクラスが自身のメンバのインターフェースを暗黙的に実装します。

インタフェースは実装しないとコンパイルエラーが出ます。

C#のinterfaceのデフォルト実装は、C#8.0で導入される予定です。 参考URL : C# 8のinterfaceのデフォルト実装 | OPCDiary

void main() {
  print(greetBob(Person('Kathy')));
  print(greetBob(Impostor('mike')));
  Person p = Impostor('emi');

  // Impostor型のgreetメソッドが呼ばれる
  print(greetBob(p));
}

class Person {
  final _name;

  Person(this._name);

  String greet(String who) => 'Hello, $who. I\'m $_name';
}

class Animal {
  String echo() {
    print('nya');
  }
}

// implementsキーワードを使用することでinterfaceを実装します。
// 複数のインタフェースを実装することができます。
class Impostor implements Person, Animal {
  // Personのインタフェース(_が付いているのでライブラリ内のみ表示される。)
  @override
  get _name => _myName;

  String _myName;

  Impostor(this._myName);

  // Personのインタフェース
  @override
  String greet(String who) => 'Hi $who. Do you know who I am?';

  // Animalのインタフェース
  @override
  String echo() => 'uhho';
}

String greetBob(Person person) => person.greet('Bob');
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        WriteLine(GreetBob(new Person("Kathy")));
        WriteLine(GreetBob(new Impostor("mike")));

        // Impostor型のGreetメソッドが呼ばれる
        IPerson p = new Impostor("emi");
        WriteLine(GreetBob(p));
    }

    static string GreetBob(IPerson person) => person.Greet("Bob");
}

class Person : IPerson
{
    private string _myName;

    public Person(string name)
    {
        this._myName = name;
    }

    // IPersonのインタフェース
    public string _name => _myName;

    // interfaceの代わりに仮想関数を使用
    internal virtual string Greet(string who) => $"Hello, {who}. I'm {_name}";

    // IPersonのインタフェース
    // 明示的に定義することで複数のインタフェースで同じ名前を使用していても
    // 判別がつくようになっている。
    string IPerson.Greet(string who)
    {
        return this.Greet(who);
    }
}

// interface実装版
// 上記のPersonクラスの仮想メソッドを使用する方法もある。
class Impostor : IPerson, Animal
{
    // IPersonのインタフェース
    public string _name => _myName;
    public string _myName;
    
    public Impostor(string name)
    {
        this._myName = name;
    }

    // IPersonのインタフェース
    public string Greet(string who)
    {
        return $"Hello, {who}. I'm override {_name}";
    }

    // Animalのインタフェース
    public string Echo()
    {
        return "nya.";
    }
}

// interface
interface Animal
{
    string Echo();
}

interface IPerson
{
    string Greet(string who);

    string _name { get; }
}
メソッドオーバーライド
void main() {
  var fox = Fox();
  fox.echo(fox);
  var dog = Dog();
  dog.echo(dog);
}
// 実行結果
// ???
// ffff
// wan wan.

class Animal {
  void echo(Animal x) {
    print('???');
  }
}

class Fox extends Animal {
  @override
  void echo(Animal x) {
    super.echo(x);
    print('ffff');
  }
}

class Dog extends Animal {
  // covariantを使用することで基底クラスより
  // 下位のクラスに限定することができる。
  @override
  void echo(covariant Dog x) {
    print('wan wan.');
  }
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var fox = new Fox();
        fox.Echo(fox);
        var dog = new Dog();
        dog.Echo(dog);
    }
}
// 実行結果
// ???
// ffff
// wan wan.

class Animal
{
    public virtual void Echo(Animal x)
    {
        WriteLine("???");
    }
}

class Fox : Animal
{
    public override void Echo(Animal x)
    {
        base.Echo(x);
        WriteLine("ffff");
    }
}

class Dog : Animal
{
    // C#ではダウンキャストを引数で行うことはできない。
    public override void Echo(Animal x)
    {
        WriteLine("wan wan.");
    }
}
演算子オーバーライド

オーバーライド可能な演算子は以下のサイトから確認しました。

Language tour | Dart

演算子のオーバーロード - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

void main() {
  final v = Vector(2, 3);
  final w = Vector(2, 2);

  assert(v + w == Vector(4, 5));
  assert(v - w == Vector(0, 1));
}

class Vector {
  final int x, y;
  Vector(this.x, this.y);

  Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

  // ==をオーバーライドする場合はGetHash()も一緒にオーバーライドする必要がります。
  // (HashSetやMap等のコレクションで同じ値のキーを効率的に探すために使われます。)

  // map keysの実装をほぼコピペ
  bool operator ==(Object v) {
    if (v is! Vector) return false;
    Vector vector = v;
    return x == vector.x && y == vector.y;
  }
  // A!=B は !(A==B)の糖衣構文なのでオーバーライドできない

  @override 
  int get hashCode {
    int result = 17;
    result = 37 * result + x.hashCode;
    result = 37 * result + y.hashCode;
    return result;
  }
}
using System;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var v = new Vector(2, 3);
        var w = new Vector(2, 2);

        Assert((v + w).Equals(new Vector(4, 5)));
        Assert((v - w).Equals(new Vector(0, 1)));
    }
}

class Vector : IEquatable<Vector>
{
    readonly int x, y;

    public Vector(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public static Vector operator +(Vector v1, Vector v2) => new Vector(v1.x + v2.x, v1.y + v2.y);
    public static Vector operator -(Vector v1, Vector v2) => new Vector(v1.x - v2.x, v1.y - v2.y);

    // タイプセーフなEqualsメソッド
    // (IEquatable<T>の実装)
    public bool Equals(Vector other)
    {
        if (other == null)
        {
            return false;
        }

        return this.x == other.x && this.y == other.y;
    }

    // C#ではほとんどの参照型ではEquals()メソッドを実装しても
    // ==演算子はオーバーライドするべきではないとされている。
    // ==演算子をオーバーライドする時は、値のようなセマンティクスを持つ参照型
    // Point、String等の基本型インスタンスに含まれるデータを変更できない場合
    // 等とされている。
    // 参考: https://dobon.net/vb/dotnet/beginner/equals.html
    public override bool Equals(object obj)
    {
        if (obj == null || this.GetType() != obj.GetType())
        {
            return false;
        }

        return this.Equals((Vector)obj);
    }
    // C#では!=はオーバーライド可能

    // Equals()メソッドを実装した時に同時にGetHashCodeもオーバーライドする。
    public override int GetHashCode()
    {
        return this.x.GetHashCode() ^ this.y.GetHashCode();
    }
}
Dartのみ】noSuchMethod()

noSuchMethod()とは ・・・ 存在しないメソッドまたはインスタンス変数を使用しようとする時に検出あるいは反応する為に、オーバーライドされるメソッドのこと。

昔の名残で残っているとのこと。

void main() {
  final foo = MockFoo();
  // MockFooクラスにはfoo()メソッドは実装していない。
  // が、noSuchMethod()をオーバーライドしているので
  // そっちで受け止める。
  foo.foo(3);
  assert(foo.piyo('fuga') == null);

  Foo fuga = MockFoo();
  assert(fuga.piyo('piyo') == null);
}

class Foo {
  // dynamic型の引数と戻り値 
  foo(x) {}
  bool piyo(String y) => false;
}

// mockクラス
class MockFoo implements Foo {
  // noSuchMethodをオーバーライドすることで、noSuchMethodErrorの発生を
  // 抑制し、柔軟な書き方ができる。
  // 開発中に役に立ちそう(Mockや設計が頻繁に変わる部分)
  @override
  noSuchMethod(Invocation invocation) {
    // fooシンボルの場合通る
    if (invocation.memberName == #foo) {
      if (invocation.isMethod &&
          invocation.positionalArguments.length == 1 &&
          invocation.namedArguments.isEmpty) {
        print(invocation);
      }
    }
    // 以下の処理をコメントアウトするとnoSuchMethod()をオーバーライドしていない
    // のでnoSuchMethodErrorが発生する。
    // return super.noSuchMethod(invocation);
  }
}

列挙型

void main() {
  assert(Color.red.index == 0);
  assert(Color.green.index == 1);
  assert(Color.blue.index == 2);

  assert(Color.values.length == 3);
}

enum Color{
  red,
  green,
  blue,
}
using System;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        Assert((int)Color.Red == 0);
        Assert((int)Color.Green == 1);
        Assert((int)Color.Blue == 2);

        Assert(Enum.GetValues(typeof(Color)).Length == 3);
    }
}

enum Color
{
    Red,
    Green,
    Blue,
}

ミックスイン

C#ではMix-inの構文はありませんがInterfaceと拡張メソッドを組み合わせてMix-inみたいなことは実現できます。

ただ、C#8.0からInterfaceのデフォルト実装が入るみたいなので、C#8.0が来るとMin-inみたいなことが簡単に実現できそうです。

今回はC#7.2なので、Interfaceと拡張メソッドを使用した方法で書いています。

void main() {
  var dog = Dog();
  dog.echo();

  var crow = Crow();
  crow.echo();
  crow.fly();

  // var piyo = Piyo();
  // piyo.fuga();
  // piyo.echo();
}
// 実行結果
// ワン
// カー
// バッサバッサ

// class Piyo extends Dog with Hoge{
//   @override
//   void echo() {
//     print('piyo');
//   }
// }

// // onの後ろはクラスでも問題ない模様
// mixin Hoge on Dog{
//   void fuga(){
//     print('フガ');
//   }
// }

class Crow extends Animal with Creature, Flyable {
  @override
  void echo() {
    print('カー');
  }
}

// onを使用することで別のmixin実装を利用することができる。
mixin Flyable on Creature {
  void fly() {
    print('バッサバッサ');
  }
}

class Dog extends Animal with Creature {
  @override
  void echo() {
    print('ワン');
  }
}

class Animal {}

// mixinキーワードはDart2.1からサポートされている。
mixin Creature {
  void echo() {}
}
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var dog = new Dog();
        dog.Echo();

        var crow = new Crow();
        crow.Echo();
        crow.Fly();
    }
}
// 実行結果
// ワン
// カー
// バッサバッサ

public static class CrowExtension
{
    internal static void Fly(this IFlyable crow)
    {
        WriteLine("バッサバッサ");
    }
}

class Dog : ICreature
{
    public void Echo()
    {
        WriteLine("ワン");
    }
}

class Crow : IFlyable
{
    public void Echo()
    {
        WriteLine("カー");
    }
}

interface IFlyable : ICreature
{
    //void Fly();
}

interface ICreature
{
    void Echo();
}

クラス変数とクラスメソッド

import 'dart:math';

void main() {
  // クラス名.クラス変数で参照
  assert(Queue.initialCapacity == 16);

  var a = Point(2, 2);
  var b = Point(4, 4);
  // クラス名.クラスメソッド名で呼び出し
  var distance = Point.distanceBetween(a, b);
  assert(2.8 < distance && distance < 2.9);
  print(distance);
}
// 実行結果
// 2.8284271247461903

class Queue {
  // クラス変数
  static const initialCapacity = 16;
}

class Point {
  num x, y;
  Point(this.x, this.y);

  // クラスメソッド
  static num distanceBetween(Point a, Point b){
    var dx = a.x - b.x;
    var dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
  }
}
using System;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        Assert(Queue.initialCapacity == 16);

        var a = new Point(2.0, 2.0);
        var b = new Point(4.0, 4.0);
        var distance = Point.DistanceBetween(a, b);
        Assert(2.8 < distance && distance < 2.9);
        WriteLine(distance);
    }
}
// 実行結果
// 2.82842712474619

// メンバがすべてstaticの場合static classにすることが可能
// static class Queue
class Queue
{
    // クラス変数
    public static readonly int initialCapacity = 16;
}

class Point
{
    double x, y;
    public Point(double x, double y)
    {
        this.x = x;
        this.y = y;
    }

    // クラスメソッド
    public static double DistanceBetween(Point a, Point b)
    {
        var dx = a.x - b.x;
        var dy = a.y - b.y;
        return Math.Sqrt(dx * dx + dy * dy);
    }
}

C#のみ】拡張メソッド

拡張メソッドとは・・・静的メソッドをインスタンスメソッドと同じ形で呼び出せるようにしたもの。

using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var dog = new Dog();
        // あたかもインスタンスメソッドのように呼べる。
        dog.Echo();
    }
}
// 実行結果
// wan.

public static class DogExtension
{
    // 拡張メソッド
    public static void Echo(this Dog dog)
    {
        // privateやprotectedアクセス修飾子が付いているメンバには
        // 拡張メソッドからは参照できない。
        WriteLine("wan.");
    }
}

public class Dog
{
    // 実行できるメソッドなし。

    private int age = 10;
}

ジェネリクス

ジェネリクスの型変数の型の制限
ジェネリクスメソッド
void main() {
  var dog = Animal<Dog>();
  print(dog);
  var fox = Animal<Fox>();
  print(fox);
}
// 実行結果
// Instance of 'Animal<Dog>
// Instance of 'Animal<Fox>

// ジェネリクスクラス
// T はCreature型を継承している型という制限をかけている。
class Animal<T extends Creature> {
  @override
  String toString() {
    return "Instance of 'Animal<$T>'";
  }
}

//派生クラス2
class Dog extends Creature {}

// 派生クラス1
class Fox extends Creature {}

// 基底クラス
class Creature {}

// ジェネリクスクラス
class Stack<T> {
  List<T> _stack = <T>[];
  T pop() {
    if (_stack.isEmpty) throw NullThrownError();
    var last = _stack.removeLast();
    return last;
  }

  void push(T value) {
    this._stack.add(value);
  }
}
using System;
using System.Collections.Generic;
using System.Linq;
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        var dog = new Animal<Dog>();
        WriteLine(dog);
        var fox = new Animal<Fox>();
        WriteLine(fox);
    }
}
// 実行結果
// Instance of 'Animal<Dog>'
// Instance of 'Animal<Fox>'

// TはCreature型を継承している制約を指定
public class Animal<T> where T : Creature
{
    public override string ToString()
    {
        return $"Instance of 'Animal<{typeof(T)}>'";
    }

}

// 派生クラス2
public class Dog : Creature { }

// 派生クラス1
public class Fox : Creature { }

// 基底クラス
public class Creature { }

// ジェネリクスクラス
public class Stack<T>
{
    List<T> _stack = new List<T>();
    public T Pop()
    {
        if (!_stack.Any()) throw new NullReferenceException();
        var last = _stack.Last();
        _stack.RemoveAt(_stack.Count - 1);
        return last;
    }
    public void Push(T value)
    {
        _stack.Add(value);
    }
}

ライブラリと可視性

ライブラリの使用

Dartは"import '(URI)'; "でライブラリを読み込むことが可能。

C# では"using (static) 名前空間; "でライブラリを読み込むことが可能。

Dartのフォルダ構成

bin
│- main.dart
L- samp_source.dart
// main.dart

// 組み込みライブラリはdart:で始まる
// import 'dart:~';
// import 'samp_source.dart';

// showをつけると、指定されている機能のみ読み込む
// asをつけることで別名にすることが可能。
import 'samp_source.dart' as Fuga show Sample;
// hideを後ろにつけると、指定されている機能以外は読み込む
import 'samp_source.dart' hide funcSample;

void main() {
  var samp = Fuga.Sample('hoge', 7);
  // こっちは問題ない。
  print(samp.name);
  // コンパイルエラー(可視性の問題)
  // print(samp._x);
}
// samp_source.dart

class Sample{
  final String name;
  int _x;

  Sample(this.name, int x)
  {
    this._x = x;
  }
}

void funcSample(int x){
  for (int i = 0; i < x; i++){
    print(i);
  }
}

C#のフォルダ構成

─ ConsoleApp3
│  ├ ConsoleApp3.csproj
│  L Program.cs
L SampSource
  ├- SampLibrary.csproj
  L- SampSource.cs
// Program.cs
// 
// usingディレクティブを使用することで、名前空間を省略することができる。
//using SampLibrary;
// using "Aliace" = 名前空間;で名前空間を別名に置き換えることが可能。
using Fuga = SampLibrary;
// using staticディレクティブを使用することで、静的クラスの名前空間を省略することができる。
// Sysmte.Diagnostics.Debug.Assert();やSystem.Diagnostics.Debug.WriteLine();を今回の記事でも使用している。
using static System.Diagnostics.Debug;
class Program
{
    static void Main(string[] args)
    {
        // SampleLibraryをFugaに置き換えている。
        var samp = new Fuga.SampSource("hoge", 7);
        // usingディレクティブを使用しない場合、以下のように完全修飾名
        // で生成することが可能。
        //var samp = new SampLibrary.SampSource("hoge", 7);
        WriteLine(samp.name);
        // internalアクセス修飾子はライブラリ外から触ることができない。
        // WriteLine(samp._x);
    }
}
// SampSource.cs
namespace SampLibrary
{
    public class SampSource
    {
        public readonly string name;
        internal int _x;

        public SampSource(string name, int x)
        {
            this.name = name;
            _x = x;
        }
    }
}
Dartのみ】ライブラリの遅延ロード

Dartはライブラリの遅延ロードが可能です。 (現状ではdart2jsのみ。Flutter、DartVMおよびdartevcでの遅延ロードはサポートしていません。)

詳細は以下を参照して下さい。

// defferred as で別名をつけておくことで遅延ロードすることが可能。
// defferred as (別名)で(別名)の名前空間にFuture loadLibrary()メソッドが追加される為注意
// 下記だとrare.loadLibrary();が追加されている。
import 'dart:collection' deferred as rare;

void main() async {

  var result = await load();
}

// ライブラリの遅延ロードはロードできるまで処理が止まるので
// 非同期処理で動かすのが望ましい。
Future load() async {
  // ライブラリのロード
  return await rare.loadLibrary();
}
ライブラリの実装

今の段階では、ライブラリを作成することはなさそうなので、必要な方は以下のサイトを参考にして下さい。

Creating packages | Dart

上記のサイトを日本語訳もありました。

Flutter - Create Library Packages (日本語訳) - Qiita

非同期処理

先物の取り扱い
非同期関数の宣言

Dartは非同期メソッドから値を取得する方法は2つあります。

  • awaitとasyncキーワードを使用する
  • Future APIを使用する

今回はDartC#もawaitとasyncキーワードを使用して非同期処理を同期処理のように記述することが可能です。
Dartはasyncキーワードを記述したメソッドはFuture\<T>型で返されてきます。
C#は非同期処理を行う場合は一般化非同期戻り値の条件を満たす型である必要があります。
  ※参考 一般非同期戻り値の条件
普通に使用する分にはTask、Task\<T>を使用すれば問題ないと思います。

import 'dart:async';

void main(List<String> args) async {
  print('await 使用');
  var awaitResult = await executeTasksWithAsync();

  print('-------------------------------------------------------------');
  print('await 結果: $awaitResult');
}
// 実行結果
// await 使用
// executeTasksWithAsync() を開始します。
// await 計算開始 --- 2019-08-25 18:43:32.179194
// await Task1が実行されました。
// await task2が実行されました。
// executeTasksWithAsync() が完了しました。
// await 計算完了 --- 2019-08-25 18:43:33.189830
// -------------------------------------------------------------
// await 結果: 1296.0


Future<num> executeTasksWithAsync() {
  print('executeTasksWithAsync() を開始します。');
  Future<num> result = calculateMultiplyAsync(1200, 1.08);
  executeTask1('await');
  executeTask2('await');
  print('executeTasksWithAsync() が完了しました。');
  return result;
}

Future<num> calculateMultiplyAsync(num term1, num term2) async {
  print('await 計算開始 --- ${DateTime.now()}');
  await Future.delayed(Duration(seconds: 1));
  var result = term1 * term2;
  print('await 計算完了 --- ${DateTime.now()}');
  return result;
}

// dummy
void executeTask1(String executeType) {
  print('$executeType Task1が実行されました。');
}

void executeTask2(String executeType) {
  print('$executeType task2が実行されました。');
}
using static System.Diagnostics.Debug;
using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
        WriteLine("await 使用");
        // WPFやUWP等のGUIで記述する際には、Task<T>.ResultやTask<T>Wait()はデッドロックの原因になるため原則使用しない。
        var awaitResult = ExecuteTasksWithAsync();
        // awaitResult.Wait();

        WriteLine("------------------------------------------------------------");
        WriteLine($"await 結果: {awaitResult.Result}");
    }
    // 実行結果
    // await 使用
    // ExecuteTasksWithAsync を開始します。
    // await 計算開始 --- 2019/08/25 18:39:22
    // await task1が実行されました。
    // await task2が実行されました。
    // ExecuteTasksWithAsync が完了しました。
    // ------------------------------------------------------------
    // await 計算完了 --- 2019/08/25 18:39:23
    // await 結果: 1296

    static Task<double> ExecuteTasksWithAsync()
    {
        WriteLine($"{nameof(ExecuteTasksWithAsync)} を開始します。");
        Task<double> result = CalculateMutiplyAsync(1200.0, 1.08);
        ExecuteTask1("await");
        ExecuteTask2("await");
        WriteLine($"{nameof(ExecuteTasksWithAsync)} が完了しました。");
        return result;
    }

    // awaitをする時には関数にasyncをつける
    static async Task<double> CalculateMutiplyAsync(double term1, double term2)
    {
        WriteLine($"await 計算開始 --- {DateTime.Now}");
        await Task.Delay(TimeSpan.FromSeconds(1));
        var result = term1 * term2;
        WriteLine($"await 計算完了 --- {DateTime.Now}");
        return result;
    }

    static void ExecuteTask1(string executeType)
    {
        WriteLine($"{executeType} task1が実行されました。");
    }
    static void ExecuteTask2(string executeType)
    {
        WriteLine($"{executeType} task2が実行されました。");
    }
}
ストリームの処理

ストリーム(Stream)はRxJavaを使われている方ならご存知だと思います。
C#のReactive ExtensionsではIObservableに相当します。
このStreamから送られてくるデータを受信(listen/Subscribe)した時に行う処理を記述することができます。

こちらの方こちらの方こちらの方のサイトは私は普段からお世話になっているサイトでReactive Extensionsを調べる時にもすごい助かりました。
ので、Reactive ExtensionsはもちろんC#に興味がある方はご覧ください。

※この項目のC#のコードはNugetからSystem.Reactiveを追加しないとエラーが発生します。

Nugetの使用方法は以下のサイトを参照して下さい。

【VisualStudio】NuGetの使い方 | ピタゴラブログ

import 'dart:async';

void main(List<String> args) async {
  print('stream define.');
  // データを受信した時の処理
  var streamController = StreamController<String>()
    ..stream.listen((data) {
      print('no1: onNext $data');
    }, onError: (e) {
      print('no1: occur error.');
    }, onDone: () {
      print('no1: on complete!');
    }, cancelOnError: true);

  var streamController2 = StreamController<String>()
    ..stream.listen((data) {
      print('no2: onNext $data');
    }, onError: (e) {
      print('no2: occur error.');
    }, onDone: () {
      print('no2: on complete!');
    }, cancelOnError: false);

  print('-------------------------------------------------------------');
  var piyo = 'piyo';
  var fuga = 'fuga';
  print('stream add');
  // データの通知
  streamController.sink.add('$piyo');
  streamController2.sink.add('$fuga');
  print('stream on error');
  // エラー発生の通知
  streamController.sink.addError(Exception());
  streamController2.sink.addError(Exception());
  // Streamの完了
  print('stream on complete');
  await streamController.sink.close();
  await streamController2.sink.close();
}
// 実行結果
// stream define.
// -------------------------------------------------------------
// stream add
// stream on error
// stream on complete
// no1: onNext piyo
// no2: onNext fuga
// no1: occur error.
// no2: occur error.
using System;
using static System.Diagnostics.Debug;
using System.Reactive.Subjects;  // NugetでSystem.Reactiveを追加する必要があります。

class Program
{
    static void Main(string[] args)
    {
        WriteLine("stream define.");
        var subject = new Subject<string>();
        // データを受信した時の処理
        var dispose = subject.Subscribe((data) =>
        {
            WriteLine($"no1: onNext {data}");
        },
        onError: (ex) =>
        {
            WriteLine($"no1: occur error.");
        },
        onCompleted: () =>
        {
            WriteLine($"no1: on complete!");
        });

        var subject2 = new Subject<string>();
        var dispose2 = subject2.Subscribe((data) =>
        {
            WriteLine($"no2: onNext {data}");
        },
        onError: (ex) =>
        {
            WriteLine($"no2: occur error.");
        },
        onCompleted: () =>
        {
            WriteLine($"no2: on complete!");
        });

        WriteLine("------------------------------------------------------------");
        var piyo = "piyo";
        var fuga = "fuga";
        WriteLine($"stream add");
        // データの通知
        subject.OnNext(piyo);
        subject2.OnNext(fuga);
        WriteLine($"stream on error");
        // エラー発生の通知
        subject.OnError(new Exception());
        WriteLine($"stream on complete");
        // IObservable<string>の完了
        subject.OnCompleted();
        subject2.OnCompleted();
        dispose.Dispose();
        dispose2.Dispose();
    }
    // 実行結果
    // stream define.
    // ------------------------------------------------------------
    // stream add
    // no1: onNext piyo
    // no2: onNext fuga
    // stream on error
    // no1: occur error.
    // stream on complete
    // no2: on complete!
}

ジェネレータ

Dartでシーケンスを遅延生成する方法として、ジェネレータを使用することができます。

実行結果を見てもらえるとわかるのですが、List\<int>を返すfor each文では、まとめて返されていますが、ジェネレータを使用したfor each文では、1秒ごとに出力されていることがわかります。

Dartの同期ジェネレータ関数を実装するには関数本体にsyncをつけ、returnをyieldに変えればOKです。
非同期ジェネレータ関数を実装するにはasync
をつけてreturnをyieldに変えればOKです。 同期ジェネレータ関数の戻り値はIterable、非同期ジェネレータ関数の戻り値はStreamです。

C#イテレータ構文を使用すれば簡単にジェネレータが作成できます。
非同期イテレータC#8.0に実装されるみたいです。

C# 8.0 Async streams | ++C++; // 未確認飛行 C ブログ

import 'dart:io';

void main(List<String> args) {
  print('generate list');
  for (var item in generateList(3)) {
    print('item: $item');
    print('time: ${DateTime.now()}');
  }

  print('------------------------------------------------------------');
  print('generate');
  for (var item in generate(3)) {
    print('item: $item');
    print('time: ${DateTime.now()}');
  }
}
// 実行結果
// generate list
// item: 0
// time: 2019-08-25 22:58:58.347908
// item: 1
// time: 2019-08-25 22:58:58.349891
// item: 2
// time: 2019-08-25 22:58:58.349891
// ------------------------------------------------------------
// generate
// item: 0
// time: 2019-08-25 22:58:59.351600
// item: 1
// time: 2019-08-25 22:59:00.352298
// item: 2
// time: 2019-08-25 22:59:01.352459

// 同期ジェネレータ関数を実装するにはsync*を追加し、をyieldに変更する。
Iterable<int> generate(int count) sync* {
  if (count < 0) return;
  for (int i = 0; i < count; i++) {
    sleep(Duration(seconds: 1));
    yield i;
  }
}

List<int> generateList(int count) {
  if (count < 0) return <int>[];
  var list = <int>[];
  for (int i = 0; i < count; i++) {
    sleep(Duration(seconds: 1));
    list.add(i);
  }
  return list;
}
using System;
using static System.Diagnostics.Debug;
using System.Reactive.Subjects;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        WriteLine("generate list");
        foreach (var item in GenerateList(3))
        {
            WriteLine($"item: {item}");
            WriteLine($"time: {DateTime.Now}");
        }

        WriteLine("------------------------------------------------------------");
        WriteLine("generate");
        foreach (var item in Generate(3))
        {
            WriteLine($"item: {item}");
            WriteLine($"time: {DateTime.Now}");
        }
    }
    // 実行結果
    // generate list
    // item: 0
    // time: 2019/08/25 23:08:09
    // item: 1
    // time: 2019/08/25 23:08:09
    // item: 2
    // time: 2019/08/25 23:08:09
    // ------------------------------------------------------------
    // generate
    // item: 0
    // time: 2019/08/25 23:08:10
    // item: 1
    // time: 2019/08/25 23:08:11
    // item: 2
    // time: 2019/08/25 23:08:12

    static IEnumerable<int> Generate(int count)
    {
        if (count < 0) yield break;

        for (int i = 0; i < count; i++)
        {
            Thread.Sleep(1000);
            yield return i;
        }
    }

    static List<int> GenerateList(int count)
    {
        if (count < 0) return new List<int>();

        var list = new List<int>();
        for (int i = 0; i < count; i++)
        {
            Thread.Sleep(1000);
            list.Add(i);
        }
        return list;
    }
}

途中ですが

30000文字に到達しそうなので、このあたりで一度切りたいと思います。(記事内の大半はコードですが...)

残項目は以下の通りです。

  • 呼び出し可能クラス
  • Isolate
  • Typedefs
  • メタデータ
  • コメント
    • 1行コメント
    • 複数行コメント
    • ドキュメントコメント

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

参考サイト