20200626のC#に関する記事は11件です。

[C#/xaml] System.Windows.Pointのリストを使って線を引く

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

System.Windows.Pointクラスと、System.Windows.Media.PointCollectionクラスを使って、xamlで作った画面に線を引きたい。
System.Drawing.PointクラスではなくSystem.Windows.Pointクラス。

流れ

  • 準備
    • System.Windows.Pointを持たせるクラスに(以下、Pointクラスと言う)
      InotifyPropertyChangedを実装する
    • PointPointCollectionに変換してくれるコンバータをつくる
  • 線を書く
    • System.Windows.PointのListをつくる
    • 好きなタイミングで、線を引きたい座標を入れたPointをListにAddする
    • OnPropertyChanged(PropertyChanged)を実行する
    • →PointのListにあった線が描画される

前提

.NET core 3.1で実験。

サンプル

画面

<Window x:Class="WpfApp57.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp57"
        xmlns:conv="clr-namespace:Converter"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        Name="root">
    <Window.Resources>
        <conv:ListPointToPointCollectionConverter x:Key="ListPointToPointCollectionConverter" />
    </Window.Resources>
    <Grid>
        <Canvas>
            <!-- 線 -->
            <Polyline Name="MyLine" Stroke="Red" StrokeThickness="1" 
                      Points="{Binding Points, ElementName=root, Converter={StaticResource ListPointToPointCollectionConverter}}"/>
            <!-- ボタン -->
            <Button Content="Button" Height="100" Canvas.Left="10" Canvas.Top="324" Width="100" Click="Button_Click"/>
        </Canvas>
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApp57
{
    /// <summary>
    /// ボタンをおしたら、サインカーブを書く
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        // ------------------------------------------------

        // サインカーブの縦方向の真ん中座標
        private double y = 150.0;

        public List<Point> Points
        {
            get { return _points; }
            set { _points = value; OnPropertyChanged(nameof(Points)); }
        }
        private List<Point> _points = new List<Point>();

        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var _ = Task.Run(() =>
            {
                this.Dispatcher.Invoke(new Action(async () =>
                {
                    for (int i = 0; i < 100; i++)
                    {
                        Points.Add(new Point((double)i, y + 150.0 * Math.Sin((double)i / (Math.PI))));
                        OnPropertyChanged(nameof(Points));
                        await Task.Delay(30);
                    }
                }));
            });

        }
    }
}

コンバータ

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;

namespace Converter
{
    public class ListPointToPointCollectionConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var points = (List<Point>)value;
            return points != null ? new PointCollection(points) : null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

備考

試してないが、コンバータを使わなくても、コード内でPointをPointCollectionにしてもよいと思う。

コード

https://github.com/tera1707/WPF-/tree/master/038_DrawLines

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WinForm] フォームクローズ中に「タスク終了待ち」をする

フォームクローズ中に「タスクの終了待ち」を行ったらデッドロックしたので、処理を考えてみました。

条件:
タスクの中でUIにアクセスしている。
スレッド内の処理はこの段階ではもう実行する必要はない。

(1) タスクの終了を「await」を使用せず、敢えて「Result や Await」でUIスレッドを止めつつ待つ
(2) フォームクローズ中の「Result や Await」で、「Invoke」や「Begin+EndInvoke」を通るとデッドロックする

public partial class frmMain : Form
{
    private Task<string> _task;
    private CancellationTokenSource _tokenSource;

    public frmMain()
    {
        InitializeComponent();
    }

    private void btnRun_Click(object sender, EventArgs e)
    {
        txtLog.Text = "AsyncFunc: Run.";

        _tokenSource = new CancellationTokenSource();

        _task = Task.Run(() => AsyncFunc(_tokenSource.Token), _tokenSource.Token);

        btnRun.Enabled = false;
        btnClose.Enabled = true;
    }

    private void btnClose_Click(object sender, EventArgs e)
    {
        btnRun.Enabled = true;
        btnClose.Enabled = false;

        txtLog.Text = "AsyncFunc: Closing.\r\n" + txtLog.Text;

        Close();
    }

    private string AsyncFunc(CancellationToken token)
    {
        try
        {
            var count = 0;
            var log = txtLog.Text;

            while (token.IsCancellationRequested == false)
            {
                var handle = BeginInvoke(new Action(() => // (2)
                {
                    txtLog.Text = "AsyncFunc: Loop(" + count++ + ").\r\n" + log;
                    txtLog.Update();
                }));

                if (handle.AsyncWaitHandle.WaitOne(300) == false) // (2)
                {
                    if (token.IsCancellationRequested == true)
                    {
                        // タスクのキャンセル中にここへ来るのは正しい
                    }
                    else
                    {
                        throw new Exception();
                    }
                }
            }

            var handle2 = BeginInvoke(new Action(() => // (2)
            {
                txtLog.Text = "AsyncFunc: Finish.\r\n" + txtLog.Text;
                txtLog.Update();
            }));

            if (handle2.AsyncWaitHandle.WaitOne(300) == false) // (2)
            {
                if (token.IsCancellationRequested == true)
                {
                    // タスクのキャンセル中にここへ来るのは正しい
                }
                else
                {
                    throw new Exception();
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }

        return "AsyncFunc: Success.";
    }

    private void frmMain_FormClosed(object sender, FormClosedEventArgs e)
    {
        _tokenSource.Cancel();

        var result = _task.Result; // (1)

        txtLog.Text = result + "\r\n" + txtLog.Text;
        txtLog.Update();

        txtLog.Text = "AsyncFunc: Closed.\r\n" + txtLog.Text;
        txtLog.Update();

        { // この部分は必要ないが、閉じる瞬間を目視するために追加しています。
            var count = 5;
            var log = txtLog.Text;
            do
            {
                txtLog.Text = count + "\r\n" + log;
                txtLog.Update();

                Thread.Sleep(1000);

            } while (count-- > 0);
        } // ここまで
    }
}

※希望通りの動作にはなったが、これが本当に正しい書き方なのかはわかりません...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

形状比較 (Huモーメント)

private (Mat, Point[][], HierarchyIndex[]) FindContours(string filepath)
{
    var src = new Mat(filepath, ImreadModes.Grayscale);

    Cv2.Threshold(src, src, thresh: 87, maxval: 255, ThresholdTypes.BinaryInv);

    Cv2.FindContours(src, out Point[][] contours, out HierarchyIndex[] hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);

    Mat edge = new Mat();
    Cv2.Canny(src, edge, threshold1: 96, threshold2: 96);

    return (edge, contours, hierarchy);
}

private void btnRun_Click(object sender, EventArgs e)
{
    Point[][] contours1;
    HierarchyIndex[] hierarchy1;

    Mat pattern;
    (pattern, contours1, hierarchy1) = FindContours(@".\pattern_nut.png");

    Cv2.ImShow("pattern", pattern);

    Point[][] contours2;
    HierarchyIndex[] hierarchy2;

    Mat data;
    (data, contours2, hierarchy2) = FindContours(@".\data.png");

    var output = new Mat();
    Cv2.CvtColor(data, output, ColorConversionCodes.GRAY2BGR);

    Cv2.ImShow("data", data);

    // ノイズ除去
    for (int i = 0; i < contours2.GetLength(0); ++i)
    {
        var area = Cv2.ContourArea(contours2[i].ToArray());

        if (area < 100)
        {
            var rect = Cv2.BoundingRect(contours2[i]);
            rect.Inflate(width: 1, height: 1);
            output.Rectangle(rect, Scalar.Black, thickness: -1);
        }
    }

    for (int i = 0; i < contours2.GetLength(0); ++i)
    {
        var area = Cv2.ContourArea(contours2[i].ToArray());

        if (area < 100)
        {
            continue;
        }

        var rect = Cv2.BoundingRect(contours2[i]);
        output.Rectangle(rect, Scalar.GreenYellow);

        var moments = Cv2.Moments(contours2[i]);
        Point centroid = new Point();
        centroid.X = (int)(moments.M10 / moments.M00);
        centroid.Y = (int)(moments.M01 / moments.M00);
        output.Circle(centroid, radius: 3, Scalar.Yellow, thickness: -1);

        double ratio = Cv2.MatchShapes(data[rect], pattern, ShapeMatchModes.I1);
        output.PutText(ratio.ToString("F3"), rect.TopLeft, HersheyFonts.HersheySimplex, fontScale: 0.8, (ratio < 0.005) ? Scalar.White : Scalar.Red);

        Cv2.MinEnclosingCircle(contours2[i], out Point2f center, out float fadius);
        var diff = center - centroid;
        double distance = Math.Sqrt(diff.X * diff.X + diff.Y * diff.Y);
        output.PutText(distance.ToString("F1"), centroid + new Point(10, 0), HersheyFonts.HersheySimplex, fontScale: 0.8, Scalar.Yellow, thickness: 2);
    }

    Cv2.ImShow("output", output);
}

7394c6479293d836cc457ac5306cbcd0[1].png
343e5c0d1c2180e2f6beef37b26fbad9[3].png
cf732495f3cc1aa373a4d6be2497e53b[1].png
2fe1da1f2dffeed36b29452765cfb8f9[1].png
bb030c5cb11803102cfed1fc84143d7a[1].png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WinForm] UIスレッド内での非同期処理

・ 非同期関数

static async Task<string> AsyncFunc()
{
    await Task.Delay(3000);
    return "OK";
}

1-1)「Result」や「Wait()」で待つ方法

private void button_Click(object sender, EventArgs e)
{
    var result = AsyncFunc().Result;
    Console.WriteLine(result);
}

※UIスレッド内でこの記述を使用して「Invoke」や「EndInvoke」を通るとデッドロックする

1-2) AsyncFunc関数を以下のように変更するとデッドロックを回避できる

static async Task<string> AsyncFunc()
{
    await Task.Delay(3000).ConfigureAwait(false);
    return "OK";
}

※ただし、タスク側がUIスレッドを占有するのでフォーム側の制御はできない

2)「async」と「await」で待つ方法

private async void button_Click(object sender, EventArgs e)
{
    var result = await AsyncFunc();
    Console.WriteLine(result);
}

※タスクとフォームでUIスレッドをシェアするのでどちらも制御はできる

3) 「Result」や「Wait()」を使うのはどんな場面?
これについては調べ切れていませんが、
例えば、unsafeなクラス内でawaitを使用した場合にエラーが発生しました。しかし、「Result」や「Wait()」であればエラーは発生しませんでした。

CS4004: unsafe コンテキストで待機することはできません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WinForm] XAML Islands を使用する

・ 実行環境
エディション: Windows 10 Pro
バージョン: 2004
OS ビルド: 19041.264

・開発環境
ワークロード: .NET デスクトップ開発
インストールの詳細: .NET Framework 4.8 開発ツール

・プロジェクト テンプレート
Windows フォーム アプリケーション (.NET Framework)

0. NuGet で Microsoft.Toolkit.Forms.UI.XamlHost をインストールする。

1. WindowsXamlHostをデザイナで貼り付けようとする。

---------------------------
Microsoft Visual Studio
---------------------------
コンポーネント 'WindowsXamlHost' を生成できませんでした。  エラー メッセージ:

 'System.TypeLoadException: Windows ランタイム型 'Microsoft.Toolkit.Win32.UI.XamlHost.IXamlMetadataContainer' が見つかりませんでした。

   場所 Microsoft.Toolkit.Forms.UI.XamlHost.WindowsXamlHostBase..cctor()'
---------------------------
OK   
---------------------------

上記のエラーが発生しました。しかし、これを解消する方法は見つかりませんでした。

2-1. デザイナを使用せずに直接コードを記述する。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        Microsoft.Toolkit.Forms.UI.XamlHost.WindowsXamlHost wxh = new Microsoft.Toolkit.Forms.UI.XamlHost.WindowsXamlHost();
    }
}

以下のエラーが発生しました。

WindowsXamlManager and DesktopWindowXamlSource are supported for apps targeting Windows version 10.0.18226.0 and later. 
Please check either the application manifest or package manifest and ensure the MaxTestedVersion property is updated.

上記エラーをぐぐると以下の様な情報にヒットします。
Using XAML Islands on Windows 10 19H1
Windows-toolkit/Microsoft.Toolkit.Win32 issues #76

1) 「アプリケーション マニフェスト ファイル」を追加する
2) 「assembly -> compatibility -> application」の下に以下コードを追加する

<!-- Windows 10 -->
<maxversiontested Id="10.0.18358.0"/>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />

とすれば良いと読み取れた。

2-2-1. 「アプリケーション マニフェスト ファイル」を追加する。

なぜかこの段階でエラーが発生しなくなりました。

2-2-2. 「assembly -> compatibility -> application」に追加する。

「2-2-1.」ですでに問題は解消しているため変化は無しです。

OSビルドが「10.0.18358.0」であったときの記事を参考にしているため、
今回の環境では不要なのかもしれないです。(詳細を調べ切れず原因は不明)

3. その他、注意点

LTSC版のWindows 10は、最新バージョンが1809(OSビルド:17763.1282)のため、
この機能は動作しませんでした。

大元の手順は以下のサイトを参考にさせて頂きました。
Windows フォームアプリケーションで最新機能に対応する XAML Islands
XAML Islands の Windows Community Toolkit でラップされたコントロール (WinForms編)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オプティカルフロー

private void btnRun_Click(object sender, EventArgs e)
{
    Task.Run(() => Play());
}

private void Play()
{
    using (var video = new VideoCapture(@".\768x576.avi"))
    {
        var color_diff = new Mat();
        var gray_diff = new Mat();
        var black_diff = new Mat();
        var hist_color = new Mat();
        var hist_gray = new Mat();
        var mask = new Mat();
        var orientation = new Mat();

        double duration = 1.0;

        var LINE_LENGTH_ALL = 60;
        var LINE_LENGTH_GRID = 20;
        var GRID_WIDTH = 40;
        var CIRCLE_RADIUS = 2;

        var frame_next = new Mat();

        var end_flag = video.Read(frame_next);
        var frame_pre = frame_next.Clone();

        var width = video.FrameWidth;
        var height = video.FrameHeight;

        var motion_history = new Mat<float>(height, width);

        int count = 0;

        var sw = System.Diagnostics.Stopwatch.StartNew();

        while (end_flag)
        {
            Cv2.Absdiff(frame_next, frame_pre, color_diff);

            Cv2.CvtColor(color_diff, gray_diff, ColorConversionCodes.BGR2GRAY);

            Cv2.Threshold(gray_diff, black_diff, 30, 1, ThresholdTypes.Binary);

            var timestamp = sw.Elapsed.TotalSeconds;

            // 履歴画像の更新
            CvOptFlow.UpdateMotionHistory(black_diff, motion_history, timestamp, duration);

            Cv2.Normalize(motion_history, hist_gray, 0, 255, NormTypes.MinMax, MatType.CV_8UC1);

            Cv2.CvtColor(hist_gray, hist_color, ColorConversionCodes.GRAY2BGR);

            // 履歴画像のモーション方向の計算
            double delta1 = 0.25;
            double delta2 = 0.05;
            int apertureSize = 5;
            CvOptFlow.CalcMotionGradient(motion_history, mask, orientation, delta1, delta2, apertureSize);

            double angle_deg;
            double angle_rad;

            // 各座標のモーション方向を緑色の線で描画
            var width_i = GRID_WIDTH;
            while (width_i < width)
            {
                var height_i = GRID_WIDTH;

                while (height_i < height)
                {
                    Cv2.Circle(hist_color,
                                width_i, height_i,
                                CIRCLE_RADIUS,
                                new Scalar(0, 255, 0),
                                2,
                                LineTypes.AntiAlias);

                    angle_deg = orientation.At<float>(height_i - 1, width_i - 1);

                    if (angle_deg > 0)
                    {
                        angle_rad = angle_deg * Math.PI / 180;

                        Cv2.Line(hist_color,
                                    width_i, height_i,
                                    (int)(width_i + Math.Cos(angle_rad) * LINE_LENGTH_GRID),
                                    (int)(height_i + Math.Sin(angle_rad) * LINE_LENGTH_GRID), 
                                    new Scalar(0, 255, 0),
                                    2,
                                    LineTypes.AntiAlias);
                    }

                    height_i += GRID_WIDTH;
                }

                width_i += GRID_WIDTH;
            }

            // 全体的なモーション方向を計算
            angle_deg = CvOptFlow.CalcGlobalOrientation(orientation, mask, motion_history, timestamp, duration);

            // 全体のモーション方向を黄色い線で描画
            Cv2.Circle(hist_color,
                       width / 2, height / 2,
                       CIRCLE_RADIUS,
                       new Scalar(0, 215, 255),
                       2,
                       LineTypes.AntiAlias);

            angle_rad = angle_deg * Math.PI / 180;

            Cv2.Line(hist_color,
                     width / 2, height / 2,
                     (int)(width / 2 + Math.Cos(angle_rad) * LINE_LENGTH_ALL),
                     (int)(height / 2 + Math.Sin(angle_rad) * LINE_LENGTH_ALL),
                     new Scalar(0, 215, 255),
                     2,
                     LineTypes.AntiAlias);

            Invoke(new Action(() =>
            {
                Cv2.ImShow("hist_color", hist_color);
            }));

            var time = (int)(count * (1000 / video.Fps) - sw.Elapsed.TotalMilliseconds);

            if (time > 0)
            {
                System.Threading.Thread.Sleep(time);
            }

            frame_pre = frame_next.Clone();
            end_flag = video.Read(frame_next);

            ++count;
        }
    }
}

4d6cb9d2bd544695d256cae5826f5892[1].png
67988123a54afd4ee75c4ef3e0d78bf3[1].png

※ソースコードはこちらのサイトを参考にさせていただきました。
OpenCVを使ったモーションテンプレート解析 @hitomatagi

※動画ファイルはこちらのサイトからダウンロードさせていただきました。
opencv-tests @shirmung

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

キャリブレーション

var src = new Mat(@".\dot_Bobbin_img.png", ImreadModes.Grayscale);

var patternSize = new Size(18, 13);

var centers = new Mat();
var found = Cv2.FindCirclesGrid(src, patternSize, centers, FindCirclesGridFlags.SymmetricGrid);

var points_img = new Mat();
Cv2.CvtColor(src.Clone(), points_img, ColorConversionCodes.GRAY2BGR);

Cv2.DrawChessboardCorners(points_img, patternSize, centers, true);

Cv2.ImShow("points", points_img);
Cv2.WaitKey();

int left_margin = 26;
int top_margin = 18;
int interval = 44;

var object_points = new Mat<Point3f>();
for (int j = 0; j < patternSize.Height; j++)
{
    for (int i = 0; i < patternSize.Width; i++)
    {
        object_points.Add(new Point3f(left_margin + i * interval, top_margin + j * interval, 0));
    }
}

var imageSize = src.Size();

var cameraMatrix = new Mat<double>(3, 3);
var distCoeffs = new Mat<double>(5, 1);

Cv2.CalibrateCamera(new[] { object_points }, new[] { centers }, imageSize, cameraMatrix, distCoeffs, out Mat[] rvecs, out Mat[] tvecs);

var newImageSize = new Size();
var newCameraMatrix = Cv2.GetOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, newImageSize, out Rect validPixROI);

var data = new Mat(@".\dot_Bobbin_img.png", ImreadModes.Grayscale);

var temp_img = new Mat();
Cv2.Undistort(data, temp_img, cameraMatrix, distCoeffs, newCameraMatrix);

var rotation = new Mat();
Cv2.Rodrigues(rvecs[0], rotation);

var transRot = new Mat<double>(3, 3);
rotation.Col(0).CopyTo(transRot.Col(0));
rotation.Col(1).CopyTo(transRot.Col(1));

var transData = new double[3,3] { { 0, 0, tvecs[0].At<double>(0) },{ 0, 0, tvecs[0].At<double>(1) }, { 0, 0, tvecs[0].At<double>(2) } };
var translate = InputArray.Create(transData).GetMat();
translate.Col(2).CopyTo(transRot.Col(2));

var dst_img = new Mat();
var m = newCameraMatrix * transRot;
Cv2.WarpPerspective(temp_img, dst_img, m, newImageSize, InterpolationFlags.WarpInverseMap);

Cv2.ImShow("temp", temp_img);
Cv2.WaitKey();

Cv2.ImShow("dst", dst_img);

33661a48b48dca01f98a042375aba368[1].png
c83a5792b630f2b553904eb8b601e50a[1].png
fb3273acdc75324b4c4ede4e3b11bef8[1].png
41534924ae29ab8249ea3a1a3bf25e62[1].png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】定義済みデリゲートとラムダ式を使用し、省略された1つの式を読み解く

概要

C#初学者をさまよい続けるこの頃・・・
なんとなくで書いていたコード「定義済みデリゲートとラムダ式を使用し、省略された1つの式」を
ようやく理解できたので、共有したいと思います。

C#を学習し、以下の知識がある初学者向けの記事になります

  • 変数への代入
  • 明示的な変換(キャスト)
  • ラムダ式
  • デリゲート(定義済みデリゲート)

定義済みデリゲートとラムダ式を使用し、省略された1つの式とは

こんなの

public void DelegateSample1()
{
    var result = ((Func<int, int, int, string>) ((stock, quantity, costprice) =>
                                                     {
                                                         var sales = stock*quantity; //売上高
                                                         var costofsales = costprice*quantity; //売上原価
                                                         return string.Format("粗利は ({0}-{1}) で求めることができます:{2}", sales, costofsales, sales - costofsales);
                                                     }))(1500, 5, 1000);
    Console.WriteLine(result);
    Console.ReadKey();
}

これも

public void DelegateSample3()
{
    var result = ((Func<DateTime, string>) (today =>
                                                     {
                                                         var lastweek = today.AddDays(-7);
                                                         return string.Format("{0}から一週間前の日時は{1}です", today.ToShortDateString(), lastweek.ToShortDateString());
                                                     }))(DateTime.Today);
    Console.WriteLine(result);
    Console.ReadKey();
}

詳しく解説

これを解説します? 実行すると現在時刻がコンソールに表示される簡単なコードです
※このコードはデリゲート/ラムダ式を介さず直接値をセットできますが、できるだけ簡素にしたかったので

public void DelegateCast1()
{
    var result = ((Func<string>) (() =>
                                      {
                                          var date = DateTime.Now.ToShortDateString(); //日付
                                          var dayOfWeek = DateTime.Now.DayOfWeek; //曜日
                                          return string.Format("現在時刻は:{0} です({1})", date, dayOfWeek);
                                      }))();

    Console.WriteLine(result);
    Console.ReadKey();
}

どういう結果が返ってくる?
var result ▶ 右辺のラムダ式内 return string.Format~ がセットされている
image.png
右辺はどういう処理を行っているのか?デリゲートの関係性は? ▶ 分解して謎を解きます

上記のコードを分解しました 

public void DelegateCast2()
{
    /*double型10.00123をint型に変換*/
    var i = (int) 10.00123;

    /*ラムダ式をFunc<string>に変換*/
    var func = (Func<string>) (() =>
                                   {
                                       var date = DateTime.Now.ToShortDateString(); //日付
                                       var dayOfWeek = DateTime.Now.DayOfWeek; //曜日
                                       return string.Format("現在時刻は:{0} です({1})", date, dayOfWeek);
                                   });

    var result = func();
    Console.WriteLine(result);
    Console.ReadKey();
}

※解説用にvar i = (int) 10.00123;を追加しました

  • ポイント1 以下2つのコードは両方とも キャスト(型変換)を実行している
var i = (int) 10.00123;
var func = (Func<string>)(() => { 省略 });

式内の()が多く、私の頭はよくバグるのですが・・・ よく見るとキャストの()が浮かんでくると思います
変数funcには ラムダ式 をキャストした Func< string > がセットされています
image.png

  • ポイント2 resultstring型
var result = func();

デリゲートはメソッドと同じように();で実行されます
変数result には 変数func実行結果 を セットしています
image.png
元の式はすべて一行にまとまっていたため、難しくみえたキャストと実行ですが
分解して変数にセットすることで何をしているか?どの値がセットされているのか?がわかりやすくなりました
分解前は名前がないFunc< string >を実行(キャスト部分は省略)();
分解後は名前があるFunc< string >を実行func();

まとめ

この式は、キャスト後のデリゲートを実行result に結果をセットしている式である
image.png
左辺: result ▶ デリゲート実行結果
右辺: ((Func<string>)キャスト(()=>{省略}) ();デリゲートを実行

省略するメリット・デメリット

メリット
コードを短くできる・余計な変数名を考えなくて良い
デメリット
ある程度の知識がないと読めない・要
C#初学者をさまよい続けるこの頃・・・
なんとなくで書いていたコード「定義済みデリゲートとラムダ式を使用し、省略された1つの式」を
ようやく理解できたので、共有したいと思います。

C#を学習し、以下の知識がある初学者向けの記事になります

  • 変数への代入
  • 明示的な変換(キャスト)
  • ラムダ式
  • デリゲート(定義済みデリゲート)

定義済みデリゲートとラムダ式を使用し、省略された1つの式とは

こんなの

public void DelegateSample1()
{
    var result = ((Func<int, int, int, string>) ((stock, quantity, costprice) =>
                                                     {
                                                         var sales = stock*quantity; //売上高
                                                         var costofsales = costprice*quantity; //売上原価
                                                         return string.Format("粗利は ({0}-{1}) で求めることができます:{2}", sales, costofsales, sales - costofsales);
                                                     }))(1500, 5, 1000);
    Console.WriteLine(result);
    Console.ReadKey();
}

これも

public void DelegateSample3()
{
    var result = ((Func<DateTime, string>) (today =>
                                                     {
                                                         var lastweek = today.AddDays(-7);
                                                         return string.Format("{0}から一週間前の日時は{1}です", today.ToShortDateString(), lastweek.ToShortDateString());
                                                     }))(DateTime.Today);
    Console.WriteLine(result);
    Console.ReadKey();
}

詳しく解説

これを解説します? 実行すると現在時刻がコンソールに表示される簡単なコードです
※このコードはデリゲート/ラムダ式を介さず直接値をセットできますが、できるだけ簡素にしたかったので

public void DelegateCast1()
{
    var result = ((Func<string>) (() =>
                                      {
                                          var date = DateTime.Now.ToShortDateString(); //日付
                                          var dayOfWeek = DateTime.Now.DayOfWeek; //曜日
                                          return string.Format("現在時刻は:{0} です({1})", date, dayOfWeek);
                                      }))();

    Console.WriteLine(result);
    Console.ReadKey();
}

どういう結果が返ってくる?
var result ▶ 右辺のラムダ式内 return string.Format~ がセットされている
image.png
右辺はどういう処理を行っているのか?デリゲートの関係性は? ▶ 分解して謎を解きます

上記のコードを分解しました 

public void DelegateCast2()
{
    /*double型10.00123をint型に変換*/
    var i = (int) 10.00123;

    /*ラムダ式をFunc<string>に変換*/
    var func = (Func<string>) (() =>
                                   {
                                       var date = DateTime.Now.ToShortDateString(); //日付
                                       var dayOfWeek = DateTime.Now.DayOfWeek; //曜日
                                       return string.Format("現在時刻は:{0} です({1})", date, dayOfWeek);
                                   });

    var result = func();
    Console.WriteLine(result);
    Console.ReadKey();
}

※解説用にvar i = (int) 10.00123;を追加しました

  • ポイント1 以下2つのコードは両方とも キャスト(型変換)を実行している
var i = (int) 10.00123;
var func = (Func<string>)(() => { 省略 });

式内の()が多く、私の頭はよくバグるのですが・・・ よく見るとキャストの()が浮かんでくると思います
変数funcには ラムダ式 をキャストした Func< string > がセットされています
image.png

  • ポイント2 resultstring型
var result = func();

デリゲートはメソッドと同じように();で実行されます
変数result には 変数func実行結果 を セットしています
image.png
元の式はすべて一行にまとまっていたため、難しくみえたキャストと実行ですが
分解して変数にセットすることで何をしているか?どの値がセットされているのか?がわかりやすくなりました
分解前は名前がないFunc< string >を実行(キャスト部分は省略)();
分解後は名前があるFunc< string >を実行func();

まとめ

この式は、キャスト後のデリゲートを実行result に結果をセットしている式である
image.png
左辺: result ▶ デリゲート実行結果
右辺: ((Func<string>)キャスト(()=>{省略}) ();デリゲートを実行

省略するメリット・デメリット

メリット
コードを短くできる・余計な変数名を考えなくて良い
デメリット
ある程度の知識がないと読めない・キャストが発生する

最後に

読めるようになった今、この書き方は割とイケてるのではないかと思っています
括弧が多いため若干の不格好はありますが・・・!どなたかの参考になれば嬉しいです?
記事内のサンプルコード(+@で同じ結果を返すその他の書き方)はGitHubにあげています
CsharpDelegateCastSample

この記事はQiita初投稿記事となります
読みづらいところもあると思いますが、最後まで見て頂きありがとうございました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめに

この記事は意識表明です。

なぜ記事を書くのか

社内ワーキング活動の一環です。

某システム会社で働く、未熟なエンジニアの知識収集のためのメモ的な何か

主にC#、SQLに関する技術の情報を収集しまとめていきます。

筆者は文章力にも不安ありなので、そちらの改善も同時に行いたい。

記事UPのペースは2週間に1記事目標

やらないとおこられる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IBM DB2 ADO.NET Data Provider コード例

概要

この記事では、IBM DB2アプリケーション開発の効率化にDataDirect Connect for .NET DB2 Data Providerを使用し、ADO.NET での典型的なデータアクセスタスクを例示したC# のソースコードを記述します。

例示コードの動作にはサンプルのテーブルが必要です。また、環境にあわせて接続文字列の修正と、プロジェクトで System.Data と DDTek.DB2 に対してusingディレクティブを指定ください。

using System.Data; using DDTek.DB2;

コードで利用するテーブル

この記事中全てのコード例は、emp 、dept テーブルを使用するので、DB2コマンドセンターまたはDB2 Data Providerで作成ください。

DB2コマンドセンターでサンプルテーブルを作成するには、スクリプトウィンドウで次のコードを実行します。

CONNECT TO <databasename> USER <username>
USING <password>
CREATE TABLE emp (
empno INT PRIMARY KEY NOT NULL,
ename VARCHAR(10) NOT NULL,
job VARCHAR(9) NOT NULL,
mgr INT,
hiredate DATE NOT NULL,
sal NUMERIC(7,2) NOT NULL,
comm NUMERIC(7,2),
dept INT NOT NULL)
insert into emp values
    (1,'JOHNSON','ADMIN',6,'12-17-1990',18000,NULL,4)
insert into emp values
    (2,'HARDING','MANAGER',9,'02-02-1998',52000,300,3)
insert into emp values
    (3,'TAFT','SALES I',2,'01-02-1996',25000,500,3)
insert into emp values
    (4,'HOOVER','SALES I',2,'04-02-1990',27000,NULL,3)
insert into emp values
    (5,'LINCOLN','TECH',6,'06-23-1994',22500,1400,4)
insert into emp values
    (6,'GARFIELD','MANAGER',9,'05-01-1993',54000,NULL,4)
insert into emp values
    (7,'POLK','TECH',6,'09-22-1997',25000,NULL,4)
insert into emp values
    (8,'GRANT','ENGINEER',10,'03-30-1997',32000,NULL,2)
insert into emp values
    (9,'JACKSON','CEO',NULL,'01-01-1990',75000,NULL,4)
insert into emp values
    (10,'FILLMORE','MANAGER',9, '08-09-1994',56000,NULL,2)
insert into emp values
    (11,'ADAMS','ENGINEER',10,'03-15-1996',34000,NULL,2)
insert into emp values
    (12,'WASHINGTON','ADMIN',6,'04-16-1998',18000,NULL,4)
insert into emp values
    (13,'MONROE','ENGINEER',10, '12-03-2000',30000,NULL,2)
insert into emp values
    (14,'ROOSEVELT','CPA',9,'10-12-1995',35000,NULL,1)
;
;
CREATE TABLE dept (
deptno INT NOT NULL,
dname VARCHAR(14),
loc VARCHAR(13))
insert into dept values (1,'ACCOUNTING','ST LOUIS')
insert into dept values (2,'RESEARCH','NEW YORK')
insert into dept values (3,'SALES','ATLANTA')
insert into dept values (4, 'OPERATIONS','SEATTLE')

次のコードで、サンプルテーブルをDB2 Data Providerで作成します。

Conn = new DB2Connection
    ("host=ncphantom;port=50000;User ID=TEST01;
Password=TEST01;Database=test");
try {
Conn.Open();
Console.WriteLine ("Connection successful!");
}
catch (DB2Exception ex) {
// Connection failed Console.WriteLine(ex.Message); return;
}
string[] DropTableSQL = {"drop table emp", "drop table dept"};
for (int x=0; x<=1; x++) {
try {
// Drop the tables, don't care if they don't exist
DB2Command DBCmd = new DB2Command(DropTableSQL[x], Conn);
DBCmd.ExecuteNonQuery();
}
catch (DB2Exception ex) { }
}
// Create the tables
string CreateEmpTableSQL = "CREATE TABLE emp (
empno INT PRIMARY KEY NOT NULL," +"ename VARCHAR(10) NOT NULL,"+ "
job VARCHAR(9) NOT NULL, " +"mgr INT," +"hiredate DATE NOT NULL,"
+ "sal NUMERIC(7,2) NOT NULL," +"comm NUMERIC(7,2)," +"
dept INT NOT NULL)";
string CreateDeptTableSQL = "CREATE TABLE dept (" +
"deptno INT NOT NULL," +"dname VARCHAR(14)," +
"loc VARCHAR(13))";
try {
DB2Command DBCmd = new DB2Command(CreateEmpTableSQL, Conn);
DBCmd.ExecuteNonQuery();
DBCmd.CommandText = CreateDeptTableSQL;
DBCmd.ExecuteNonQuery();
}
catch (Exception ex) {
//Create tables failed Console.WriteLine (ex.Message); return; }
// Now insert the records
string[] InsertEmpRecordsSQL = {
"insert into emp values
    (1,'JOHNSON','ADMIN',6,'12-17-1990',18000,NULL,4)",
"insert into emp values
    (2,'HARDING','MANAGER',9,'02-02-1998',52000,300,3)",
"insert into emp values
    (3,'TAFT','SALES I',2,'01-02-1996',25000,500,3)",
"insert into emp values
    (4,'HOOVER','SALES I',2,'04-02-1990',27000,NULL,3)",
"insert into emp values
    (5,'LINCOLN','TECH',6,'06-23-1994',22500,1400,4)",
"insert into emp values
    (6,'GARFIELD','MANAGER',9,'05-01-1993',54000,NULL,4)",
"insert into emp values
    (7,'POLK','TECH',6,'09-22-1997',25000,NULL,4)",
"insert into emp values
    (8,'GRANT','ENGINEER',10,'03-30-1997',32000,NULL,2)",
"insert into emp values
    (9,'JACKSON','CEO',NULL,'01-01-1990',75000,NULL,4)",
"insert into emp values
    (10,'FILLMORE','MANAGER',9,'08-09-1994',56000,NULL,2)",
"insert into emp values
    (11,'ADAMS','ENGINEER',10,'03-15-1996',34000,NULL,2)",
"insert into emp values
    (12,'WASHINGTON','ADMIN',6,'04-16-1998',18000,NULL,4)",
"insert into emp values
    (13,'MONROE','ENGINEER',10,'12-03-2000',30000,NULL,2)",
"insert into emp values
    (14,'ROOSEVELT','CPA',9, '10-12-1995',35000,NULL,1)"};
string[] InsertDeptRecordsSQL = {
"insert into dept values (1,'ACCOUNTING','ST LOUIS')",
"insert into dept values (2,'RESEARCH','NEW YORK')",
"insert into dept values (3,'SALES','ATLANTA')",
"insert into dept values (4, 'OPERATIONS','SEATTLE')"};
// Insert dept table records first
for (int x = 0; x < InsertDeptRecordsSQL.Length; x++){
try {
DB2Command DBCmd = new DB2Command(InsertDeptRecordsSQL[x], Conn);
DBCmd.ExecuteNonQuery();
}
catch (Exception ex) {
Console.WriteLine (ex.Message); return;
}
}
// Now the emp table records
for (int x = 0; x < InsertEmpRecordsSQL.Length; x++) {
try {
DB2Command DBCmd = new DB2Command(InsertEmpRecordsSQL[x], Conn);
DBCmd.ExecuteNonQuery();
}
catch (Exception ex) {
Console.WriteLine (ex.Message); return;
}
}
// Close the connection
Conn.Close();

DataReaderでデータを取得する

DataReader は最も速くデータベースからデータを取得できますが、最も自由度が低い方法です。データはリードオンリーで、一方通行のストリームデータとして1回につき1レコードが返されます。

多数の複数レコードを高速に取得する必要がある場合は、DataReader はDataSet より少メモリーで済みます。DataSet は結果の保持に多くのメモリーを必要とします。

次に、DB2で簡単なクエリーを実行し、DataReader でクエリ結果の読み取り方を示します。

// Open connection to DB2 database
DB2Connection Conn;
Conn = new DB2Connection
("host=ncphantom;port=50000;User ID=TEST01;
Password=TEST01;Database=test");
try {
Conn.Open();
Console.WriteLine ("Connection successful!");
}
catch (DB2Exception ex) {
// Connection failed Console.WriteLine(ex.Message);
return;
}
// Create a SQL command
string strSQL = "SELECT ename FROM emp WHERE sal>50000";
DB2Command DBCmd = new DB2Command(strSQL, Conn);
DB2DataReader myDataReader;
myDataReader = DBCmd.ExecuteReader();
while (myDataReader.Read()) {
Console.WriteLine("High salaries: "
+ myDataReader["ename"].ToString());
}
myDataReader.Close();
// Close the connection
Conn.Close();

ローカルトランスレーション コード例

次に、ローカル・トランザクションの使い方を示します。

DB2Connection DBConn;
DBConn = new DB2Connection
    ("host=ncphantom;port=50000;User ID=TEST01;
Password=TEST01;Database=test");
DB2Command DBCmd = new DB2Command();
DB2Transaction DBTxn = null;
try {
DBConn.Open();
DBTxn = DBConn.BeginTransaction();
// Set the Connection property of the Command object
DBCmd.Connection = DBConn;
// Set the text of the Command to the INSERT statement
DBCmd.CommandText =
"insert into emp VALUES
    (15,'HAYES','ADMIN',6, {d'2002-04-17'},18000,NULL,4)";
// Set the transaction property of the Command object
DBCmd.Transaction = DBTxn;
// Execute the statement with ExecuteNonQuery, because we are
// not returning results DBCmd.ExecuteNonQuery();
// Now commit the transaction
DBTxn.Commit();
} catch (Exception ex) {
// Display any exceptions Console.WriteLine (ex.Message);
// If anything failed after the connection was opened,
// roll back the transaction
if (DBTxn != null) {
DBTxn.Rollback();
}
}
// Close the connection
DBConn.Close();

CommandBuilder コード例

DB2のempテーブルに対し、CommandBuilder でコードを作成します。

DB2Connection DBConn;
DBConn = new DB2Connection
("host=ncphantom;port=50000; User ID=TEST01;
Password=TEST01;Database=test");
DB2DataAdapter myDataAdapter = new DB2DataAdapter();
DB2Command DBCmd = new DB2Command("select * from emp",DBConn);
myDataAdapter.SelectCommand = DBCmd;
// Set up the CommandBuilder
DB2CommandBuilder CommBuild =
new DB2CommandBuilder(myDataAdapter);
DataSet myDataSet = new DataSet();
try {
DBConn.Open();
myDataAdapter.Fill(myDataSet);
// Now change the salary of the first employee
DataRow myRow;
myRow = myDataSet.Tables["Table"].Rows[0]; myRow["sal"] = 95000;
// Tell the DataAdapter to resync with the DB2 server.
// Without the CommandBuilder, this line would fail.
myDataAdapter.Update(myDataSet);
} catch (Exception ex) {
// Display any exceptions
Console.WriteLine (ex.Message);
}
// Close the connection
DBConn.Close();

DataSetでDB2のデータを更新する

行を更新する場合、DataSet はデータアダプタのUpdateCommand で提供されるSQLを使用します。

以下のように、Update 文はプライマリ―キーや更新したい列などのユニークな識別子を含むパラメータを使用できます。

[C#]

string updateSQL As String =
"UPDATE emp SET sal = ?, job = ? + = WHERE empno = ?;

パラメータクエリでは作成時のパラメータを定義します。DB2 Data Providerのパラメータについてはこちらを参照ください。次のコード例は、Parameters.Add メソッドを使用し、Update 文のパラメータを作成し、DataSet に入力し、プログラムでDataSet を変更し、変更をデータベースに書き込み同期します。

DB2Connection con = new DB2Connection("host=ncphantom;port=50000;
User ID=TEST01;Password=TEST01;Database=test");
try {
string selectText = "select sal, job, empno from emp";
string updateText =
    "update emp set sal = ?, job = ? where empno = ?";
DB2DataAdapter adapter = new DB2DataAdapter(selectText, con);
DB2Command updateCommand = new DB2Command(updateText, con);
adapter.UpdateCommand = updateCommand;
updateCommand.Parameters.Add("sal", DB2DbType.Decimal, 7, "sal");
updateCommand.Parameters.Add("job", DB2DbType.VarChar, 9, "job");
updateCommand.Parameters.Add("empno", DB2DbType.Integer,
    15, "empno");
DataSet myDataSet = new DataSet("emp");
adapter.Fill(myDataSet, "emp");
// Give employee number 12 a promotion and a raise
DataRow changeRow = myDataSet.Tables["emp"].Rows[11];
changeRow["sal"] = "35000";
changeRow["job"] = "MANAGER";
// Send back to database
adapter.Update(myDataSet, "emp");
myDataSet.Dispose();
} catch (Exception ex) {
// Display any exceptions
Console.WriteLine (ex.Message); }
// Close the connection con.Close();

DB2 のストアドプロシージャを呼び出す

Command オブジェクトを使用して、ストアドプロシージャを呼び出します。ストアドプロシージャを呼び出すには、Command オブジェクトのCommandType にストアドプロシージャをセットするか、ODBC/JDBCエスケープ文字を使用します。

以下はCommand オブジェクトのCommandType にストアドプロシージャをセットし、DB2でストアドプロシージャを実行。ストアドプロシージャの出力パラメータの値を取得する例です(要empテーブル)。

ストアドプロシージャの作成

Data ProviderまたはDB2コマンドセンターのいずれかで、次のストアドプロシージャを作成できます。

Data Providerでストアドプロシージャを作成するには、次のコードを実行します。

{ DB2Connection Conn = new DB2Connection
    ("host=ncphantom;port=50000;User ID=TEST01;
Password=TEST01;Database=test");
try {
Conn.Open();
Console.WriteLine ("Connection successful!");
} catch (DB2Exception ex) {
// Connection failed
Console.WriteLine(ex.Message);
return;
}
string DropProcSQL = "DROP PROCEDURE GetEmpSalary";
try {
DB2Command DBCmd = new DB2Command(DropProcSQL, Conn);
DBCmd.ExecuteNonQuery();
} catch (Exception ex) {
// Do nothing. Don't care if the drop fails.
}
string CreateProcSQL =
"CREATE PROCEDURE GetEmpSalary (in inempno int ,out outsal char(7))"
+" Language SQL reads SQL data BEGIN " +" SELECT CAST(sal AS CHAR(7))
INTO outsal from emp where empno = inempno;" +"END";
try {
DB2Command DBCmd = new DB2Command(CreateProcSQL, Conn);
DBCmd.ExecuteNonQuery();
} catch (Exception ex) {
//Create procedure failed
Console.WriteLine (ex.Message);
return;
} }

ストアドプロシージャの実行

DB2コマンドセンターで次のSQLを実行します。

CREATE PROCEDURE GetEmpSalary (in inempno int ,out outsal char(7)) Language SQL reads SQL data SELECT char(sal) INTO outsal from emp where empno = inempno Executing the stored procedure

以下はData ProviderでGetEmpSalaryストアドプロシージャを実行し、出力パラメータの値を取得する例です。

// Open connection to DB2 database
DB2Connection Conn;
Conn = new DB2Connection
("host=ncphantom;port=50000;User ID=TEST01;Password=TEST01;Database=test");
try { Conn.Open();
Console.WriteLine ("Connection successful!");
} catch (DB2Exception ex) {
// Connection failed Console.WriteLine(ex.Message);
return;
}
// Make a command object for the stored procedure
// You can set the CommandType of the Command object
// to StoredProcedure or use escape syntax
DB2Command DBCmd = new DB2Command("GetEmpSalary",Conn);
DBCmd.CommandType = CommandType.StoredProcedure;
DBCmd.Parameters.Add("inempno", DB2DbType.Integer, 7, "inempno");
DBCmd.Parameters[0].Value = 1;
DBCmd.Parameters.Add("outsal", DB2DbType.Char, 7, "outsal");
DBCmd.Parameters[1].Direction = ParameterDirection.Output;
DB2DataReader myReader;
try {
myReader = DBCmd.ExecuteReader();
while (myReader.Read()){}
Console.WriteLine
    ("Salary: " + Convert.ToString(DBCmd.Parameters[1].Value));
} catch (Exception ex) {
// Display any exceptions
Console.WriteLine (ex.Message);
}
// Close the connection
Conn.Close();
}

スカラ値を取得するコード例

Command オブジェクトのExecuteScalar メソッドを使用して、sum やcount など1件の値をデータベースから返すことができます。ExecuteScalar メソッドは、result set の最初の行における最初の列を返します。Result set に1行、1列しかないことが既に判明している場合は、このメソッドで値取得のスピードを上げられます。
以下はempテーブルから指定されたグループの件数を取得する例です。

// Open connection to DB2 database
DB2Connection DBConn;
DBConn = new DB2Connection("host=ncphantom;port=50000;User ID=TEST01;
Password=TEST01;Database=test");
try {
DBConn.Open();
Console.WriteLine ("Connection successful!");
} catch (DB2Exception ex) {
// Connection failed
Console.WriteLine(ex.Message);
return;
}
// Make a command object
DB2Command salCmd = new DB2Command("select count(sal) from emp
where sal>50000",DBConn);
try {
int count = (int)salCmd.ExecuteScalar();
Console.WriteLine("Count of Salaries >$50,000 : "
+ Convert.ToString(count));
} catch (Exception ex) {
// Display any exceptions Console.WriteLine (ex.Message);
}
// Close the connection
DBConn.Close();
}

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PromiseとTaskCompletionSource

非同期処理に使う JavaScript の Promise と .NET Framework (C#) の TaskCompletionSource を比較します。

概要

Promise や TaskCompletionSource のどちらか片方の知識から、比較によってもう一方の取っ掛かりにするのが狙いです。

JavaScript のコードは以下の記事から一部引用します。Promise の概要もこちらで説明しています。

Promise は await での利用に限定して、.then() は利用しません。

JavaScript は Deno を想定してトップレベルで await を使用します。ただし最後の Web Speech API の例は、ブラウザで動かす必要があるため async 関数で囲みます。

待機

定番の setTimeoutPromise でラップしたものです。

JavaScript
function wait(timeout) {
    return new Promise((resolve, reject) => setTimeout(resolve, timeout));
}

await wait(1000);
console.log(1);
await wait(2000);
console.log(2);
await wait(3000);
console.log(3);

C# に移植します。用意されている Task.Delay() を使うだけです。

C#
using System;
using System.Threading.Tasks;

class Test
{
    static async Task Main()
    {
        await Task.Delay(1000);
        Console.WriteLine(1);
        await Task.Delay(2000);
        Console.WriteLine(2);
        await Task.Delay(3000);
        Console.WriteLine(3);
    }
}
実行結果
1
2
3

TaskCompletionSource

.NET Framework で JavaScript の Promise に仕様が近いのは TaskCompletionSource です。

resolve

resolve に相当するのは SetResult です。簡単な例で比較します。

JavaScript
let p = new Promise((resolve, reject) => resolve(1));
console.log(await p);
C#
using System;
using System.Threading.Tasks;

class Test
{
    static async Task Main()
    {
        var tcs = new TaskCompletionSource<int>();
        tcs.SetResult(1);
        Console.WriteLine(await tcs.Task);
    }
}
実行結果
1

※ この例では非同期の嬉しさがありませんが、後で SetResult が遅延する例を示します。?音声合成

SetResult の名前のニュアンスは「await で取り出すための結果をセットする」という感じでしょうか。個人的には分かりやすい名前だと思います。

reject

reject に相当するのは SetException です。簡単な例で比較します。

JavaScript
function test(f) {
    return new Promise((resolve, reject) => {
        if (f) resolve(1); else reject(new Error());
    });
}

try {
    console.log(await test(true));
    console.log(await test(false));
} catch (e) {
    console.log(e);
}
C#
using System;
using System.Threading.Tasks;

class Test
{
    static Task<int> test(bool f)
    {
        var tcs = new TaskCompletionSource<int>();
        if (f) tcs.SetResult(1); else tcs.SetException(new Exception());
        return tcs.Task;
    }

    static async Task Main()
    {
        try
        {
            Console.WriteLine(await test(true));
            Console.WriteLine(await test(false));
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
}
実行結果
1
System.Exception: 種類 'System.Exception' の例外がスローされました。
   場所 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   場所 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   場所 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   場所 Test.<Main>d__1.MoveNext()

わざと例外を発生させています。

TaskCompletionSource の模倣

コールバックの引数を外に出せば TaskCompletionSource が模倣できます。

JavaScript
class TaskCompletionSource {
    constructor() {
        this.Task = new Promise((resolve, reject) => {
            this.SetResult    = resolve;
            this.SetException = reject;
        });
    }
}

function test(f) {
    let tcs = new TaskCompletionSource();
    if (f) tcs.SetResult(1); else tcs.SetException(new Error());
    return tcs.Task;
}

try {
    console.log(await test(true));
    console.log(await test(false));
} catch (e) {
    console.log(e);
}

Promise の模倣

SetResultSetException をコールバックに引数で渡せば Promise が模倣できます。

C#
using System;
using System.Threading.Tasks;

class Promise<T>
{
    private TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
    public static implicit operator Task<T>(Promise<T> p) => p.tcs.Task;
    public Promise(Action<Action<T>, Action<Exception>> action) =>
        action(tcs.SetResult, tcs.SetException);
}

class Test
{
    static Task<int> test(bool f)
    {
        return new Promise<int>((resolve, reject) => {
            if (f) resolve(1); else reject(new Exception());
        });
    }

    static async Task Main()
    {
        try
        {
            Console.WriteLine(await test(true));
            Console.WriteLine(await test(false));
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
}

同じようなことを試みた記事です。

.then().catch() を模倣しようとすると、かなり面倒なことになるようです。

キャンセル

TaskCompletionSource ではキャンセルもサポートされます。(SetCanceled

Promise には対応するものがないため、キャンセルを実装するには工夫が必要です。

比較は省略します。

音声合成

コールバックで終了やエラーの通知が来るタイプとして、Web Speech API の例を WinRT に移植して比較します。

JavaScript
function speak(lang, text) {
    return new Promise((resolve, reject) => {
        let u = new SpeechSynthesisUtterance(text);
        u.lang = lang;
        u.onend = resolve;
        u.onerror = reject;
        speechSynthesis.speak(u);
    });
}

(async function () {
    try {
        await speak("en", "Hello, world!");
        await speak("fr", "Bonjour, monde !");
        await speak("ja", "こんにちは、世界!");
    } catch (e) {
        console.log(e);
    }
})();
C#
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Media.Core;
using Windows.Media.SpeechSynthesis;
using Windows.Media.Playback;

class Program
{
    static Task Speak(string lang, string text)
    {
        var tcs = new TaskCompletionSource<int>();
        try
        {
            var voice = SpeechSynthesizer.AllVoices.First(v => v.Language.StartsWith(lang));
            var synthesizer = new SpeechSynthesizer();
            var player = new MediaPlayer();
            synthesizer.Voice = voice;
            var stream = synthesizer.SynthesizeTextToStreamAsync(text).AsTask().Result;
            player.Source = MediaSource.CreateFromStream(stream, stream.ContentType);
            player.MediaEnded += (sender, o) => tcs.SetResult(0);
            player.Play();
        }
        catch (Exception e)
        {
            tcs.SetException(e);
        }
        return tcs.Task;
    }

    static async Task Main()
    {
        try
        {
            await Speak("en", "Hello, world!");
            await Speak("fr", "Bonjour, monde !");
            await Speak("ja", "こんにちは、世界!");
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
}

JavaScript では API に任せている部分を WinRT では記述しているためやや混み入っています。MediaEnded のイベントハンドラの中から SetResult を呼んでいるのがポイントです。await Speak() は再生が終了するまで待ちます。

WinRT での音声合成については以下の記事を参照してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む