20200703のC#に関する記事は12件です。

MainWindow.xamlでアナログ時計を滑らかに

C#でアナログ時計を作ってみた

だいたい9時6分33秒くらい?かな
秒針がとても滑らかに動きます
そんな超単純なアニメーションでxamlの実力を知りました
スクリーンショット 2020-07-03 21.20.10.jpg
早速、xamlコードは以下のようになりました。

MainWindow.xaml
<Window x:Class="MainWindow.AnalogClock"
        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"
        mc:Ignorable="d"
        Title="アプリケーション課題3:Analog Clock" Height="500" Width="500" 
        Loaded="Window_Loaded" >
    <Window.Resources>
        <Storyboard x:Key="SecondHand" x:Name="SecondHand" RepeatBehavior="Forever" >
            <DoubleAnimation
                Duration="0:1:0" From="0" To="360"
                Storyboard.TargetName="SecondLine"
                Storyboard.TargetProperty="(Line.RenderTransform).(RotateTransform.Angle)"
            />
        </Storyboard>
        <Storyboard x:Key="MinuteHand" x:Name="MinuteHand" RepeatBehavior="Forever">
            <DoubleAnimation
                Duration="1:0:0" From="0" To="360"
                Storyboard.TargetName="MinuteLine"
                Storyboard.TargetProperty="(Line.RenderTransform).(RotateTransform.Angle)"
            />
        </Storyboard>
        <Storyboard x:Key="HourHand" x:Name="HourHand" RepeatBehavior="Forever">
            <DoubleAnimation
                Duration="12:0:0" From="0" To="360"
                Storyboard.TargetName="HourLine"
                Storyboard.TargetProperty="(Line.RenderTransform).(RotateTransform.Angle)"
            />
        </Storyboard>
    </Window.Resources>
    <Canvas Width="400" Height="400">
        <Line x:Name="HourLine"  Stroke="Black" Fill="Black" 
              X1="200" Y1="200" X2="200" Y2="80"              
               StrokeThickness="3" >
            <Line.RenderTransform>
                <RotateTransform  x:Name="AngleHour" Angle="0" 
                                  CenterX="200" CenterY="200"/>
            </Line.RenderTransform>
        </Line>
        <Line x:Name="MinuteLine"  Stroke="Black" Fill="Black" 
              X1="200" Y1="200" X2="200" Y2="40"              
               StrokeThickness="2" >
            <Line.RenderTransform>
                <RotateTransform x:Name="AngleMinute"  Angle="0" 
                                 CenterX="200" CenterY="200"/>
            </Line.RenderTransform>
        </Line>
        <Line x:Name="SecondLine"  Stroke="Black" Fill="LightGray"
              X1="200" Y1="240" X2="200" Y2="30"  >
            <Line.RenderTransform>
                <RotateTransform x:Name="AngleSecond" Angle="0"  
                                 CenterX="200" CenterY="200"/>
            </Line.RenderTransform>
        </Line>
        <Ellipse Fill="Black" Width="10" Height="10" HorizontalAlignment="Center" 
                 VerticalAlignment="Center" 
                 Canvas.Top="195" Canvas.Left="195"/>
    </Canvas>
</Window>

さらにC#サイドのコードは以下の通り

MainWindows.xaml.cs
// ********************************************************
// *
// * 処理内容:滑らかなアナログ時計の動作を実現
// *
// *        MainWindow.xaml.cs :アナログ時計表示
// *
// *        2020.07.03 ProOJI
// *
// ********************************************************

using System;
using System.Windows;
using System.Windows.Media.Animation;

namespace MainWindow
{
    /// <summary>
    /// アナログ時計のクラス
    /// </summary>
    public partial class AnalogClock : Window
    {
        /// <summary>
        /// 初期設定
        /// </summary>
        public AnalogClock()
        {
            InitializeComponent();
            InitializeAngle();
        }
        /// <summary>
        /// アナログ時計のアニメーション
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // 時間の針
            StartAnimation("HourHand", this.AngleHour.Angle);
            // 分の針
            StartAnimation("MinuteHand", this.AngleMinute.Angle);
            // 秒の針
            StartAnimation("SecondHand", this.AngleSecond.Angle);
        }
        /// <summary>
        /// 針の角度の初期設定
        /// </summary>
        /// <returns>なし</returns>
        void InitializeAngle()
        {
            DateTime dt = DateTime.Now;
            this.AngleSecond.Angle = dt.Second * 360.0 / 60.0;
            this.AngleMinute.Angle = (dt.Minute + dt.Second / 60.0) * 360.0 / 60.0;
            this.AngleHour.Angle = (dt.Hour + dt.Minute / 60.0) * 360.0 / 12;
        }

        /// <summary>
        /// 時計の針のアニメーション表示
        /// </summary>
        /// <param name="name">各針の名称</param>
        /// <param name="angle">針の角度</param>
        /// <returns>なし</returns>
        private void StartAnimation(string name, double angle)
        {
            var sb = this.Resources[name] as Storyboard;
            var da = sb.Children[0] as DoubleAnimation;
            da.From = angle;
            da.To = da.From + 360.0;
            sb.Begin();
        }
    }
}

まとめ

XAMLのみで、かなりのアニメーションを作ることができるのはすごい
以上!( ̄∀ ̄)

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

Xamarin.FormsでAndroid向けTwitterアプリケーションを開発したときに困ったこと ~メディア編~

N.Mです.

こちらの記事の後編です.ここでは,Xamarin.Formsでメディア周りの処理で困ったことと解決策をまとめました.

Twitterへのメディア投稿

参考:https://github.com/kwmt/WebViewInputSample

デフォルトの状態でも、Twitterのメディアの投稿自体はブラウザと同様にできるのですが、JPEG画像はできても、PNGやGIF、動画については投稿できませんでした。デフォルトだと、HTMLの<input>タグのacceptオプションで複数種類のメディアをサポートしていても、1番先頭にあるものしか認識されないみたいです。

投稿するメディアの選択ダイアログを開く時には、前回にも出てきたFormsWebChromeClientクラスのOnShowFileChooserメソッドが呼ばれるようです。ここを書き換えて、複数種類のメディアを選択できるダイアログのIntentを起動します。

//FormsWebChromeClientクラス内

//mainActivityはXamarin.AndroidでのMainActivity。FormsWebChromeClientにstatic変数として持たせて、
//MainActivityのOnCreateでそのstatic変数にMainActivity自身を渡す。

//REQUEST_IMAGE_CODEにはFormsWebChromeClient内で適当な値に設定しています。

public override bool OnShowFileChooser(Android.Webkit.WebView webView, IValueCallback filePathCallback, FileChooserParams fileChooserParams)
{
    Intent intent = new Intent(Intent.ActionOpenDocument);
    intent.AddCategory(Intent.CategoryOpenable);
    intent.SetType("*/*");
    intent.PutExtra(Intent.ExtraMimeTypes, fileChooserParams.GetAcceptTypes());
    mainActivity.intentCallback = filePathCallback;
    mainActivity.StartActivityForResult(intent, REQUEST_IMAGE_CODE);

    return true;
}

複数種類メディアを選択できるようにするのにintent.PutExtra(Intent.ExtraMimeTypes, fileChooserParams.GetAcceptTypes());が必要です。また、複数回メディア投稿ボタンを押しても機能するように、MainActivityでIntentのからの結果を処理する際にfilePathCallback.onReceiveValueメソッドを呼ぶ必要があります。

MainActivityに変数intentCallbackをもたせて、mainActivity.intentCallback = filePathCallback;で登録することで、MainActivity内で呼べるようにしてあります。

MainActivityでは上記で起動したIntentからの結果を処理するように、下記のようにOnActivityResultメソッドをオーバーライドします。

//MainActivity内

//intentCallback
//REQUEST_IMAGE_CODEにはFormsWebChromeClientでの値と同じにする。

public IValueCallback intentCallback;

protected override void OnActivityResult(int requestCode, Result resultCode, Intent resultData)
{
    if (requestCode == REQUEST_IMAGE_CODE)
    {
        if (resultCode == Result.Ok)
        {
            intentCallback.OnReceiveValue(new Android.Net.Uri[] { resultData.Data });
            intentCallback = null;
        }
        else if (resultCode == Result.Canceled)
        {
            intentCallback.OnReceiveValue(null);
            intentCallback = null;
        }
    }
}

これで、起動したIntentに対し、ファイルが選ばれた場合も、キャンセルされた場合も処理されるようになっております。FormsWebChromeClientMainActivityを修正することで、JPEG以外のPNGなどの画像や、動画も選択できるようになり、投稿できるようになります。

ストレージ参照の許可

参考:https://docs.microsoft.com/ja-jp/xamarin/android/app-fundamentals/permissions?tabs=windows

画像の保存をできるようにするためには、アプリケーションに権限を付与する必要があり、これもXamarin.Formsだけではできず、Android側での処理が必要になります。

まず、Androidマニフェストでこのアプリがどの権限を使うかを指定する必要があります。Visual Studioの場合はAndroidプロジェクトのプロパティから指定できます。保存の場合はここでWRITE_EXTERNAL_STORAGEを指定します。

これだけだと、アプリのユーザが設定で権限を有効にしない限りは、アプリに権限が付与されないので、起動時に権限がなければユーザに権限を付与する許可を得るためのダイアログを開くようにする必要があります。Androidプロジェクト側のMainActivityOnCreateメソッドで以下を呼び出します。

//yourCodeは26である必要はなく、適当な数字で大丈夫ですが、後述のOnRequestPermissionResultでのものと一致させる必要はあります。

const int yourCode = 26;
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted)
{
    ActivityCompat.RequestPermissions(this, new String[] { Manifest.Permission.WriteExternalStorage }, yourCode);
}

許可を得られたかどうかの結果を確認するために、MainActivityOnRequestPermissionResultメソッドをオーバーライドします。

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
    const int yourCode = 26;
    if (requestCode == yourCode)
    {
        for (int i = 0; i < grantResults.Length; i++)
        {
            if (grantResults[i] != Permission.Granted)
            {
                Android.OS.Process.KillProcess(Android.OS.Process.MyPid());
            }
        }
    }
    else
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

上記の実装では許可が得られなかったら、if (grantResults[i] != Permission.Granted)の中で正常に機能しないとしてアプリケーションを落としていますが、保存処理だけできないようにフラグを立てるなどというように修正すれば、一部機能制限に変更することもできます。

UrlからメディアのByte情報を取得

参考:https://stackoverflow.com/questions/41337487/how-to-download-image-from-url-and-save-it-to-a-local-sqlite-database

Xamarin.FormsのWebViewだと、画像長押しによる画像の保存ができなかったので、動画ダウンロードもできるようにするために保存処理を自前で作ることにしました。URL自体は画像の場合はHTMLから、動画の場合はTwitter REST APIから取得できましたが、このURLからAndroidに保存するデータ(Byte情報)の取得で少しつまづき、調べました。

どうやら、System.Net.HttpHttpClientを利用すれば、実現できるようです。これを使うことで、UrlのところにHttpでデータを要求し、返ってきたレスポンスから画像や動画などのByte情報を取得できます。

//Timeoutの時間も設定できます。
HttpClient httpClient = new HttpClient{Timeout = TimeSpan.FromSeconds(15)};

//取得した画像や動画のByte列を格納する変数
byte[] imageData;

//downloadUrlは画像や動画のUrl
using (HttpResponseMessage httpResponse = await httpClient.GetAsync(downloadUrl))
{
    if (httpResponse.StatusCode == System.Net.HttpStatusCode.OK)
    {
        //正常に取得できたというレスポンス(System.Net.HttpStatusCode.OK)ならデータを取得
        imageData = await httpResponse.Content.ReadAsByteArrayAsync();
    }
}

DCIMフォルダへのメディアの保存

参考:https://www.c-sharpcorner.com/article/local-file-storage-using-xamarin-form/(PCLStorageについて)
https://forums.xamarin.com/discussion/175085/i-need-to-save-ad-in-image-in-dcim(DCIMのパスについて)

今回はDCIMフォルダにダウンロードしてきた画像や動画を保存することにしました。

ファイルシステムはプラットフォームごとに全然違います。前にXamarin.Androidのみで画像のロードや、ロードした先に追加保存などをやろうとしたときは、画像ロードのIntentから取得できるものがUrlなので、このUrlをMesiaStoreに投げて、パスに変換するなど結構大変でした。少し身構えていましたが、DCIMフォルダに新規に画像を保存するだけなら、そんなに大変ではないみたいです。

またPCLStorageを使えば、Xamarin.Formsのプロジェクトで、各プラットフォーム共通の処理として、データ保存処理を書けるようです。(パスの取得は各プラットフォームごとに処理を書く必要がありますが)

PCLStorageではXamarin.Formsプロジェクト内で以下のようにフォルダ作成や保存処理を書くことができます。

//DCIMフォルダの取得(DCIMPathの取得は後述)
IFolder DCIMFolder = await FileSystem.Current.GetFolderFromPathAsync(DCIMPath);

//DCIM内に別につくる保存用フォルダ
IFolder saveFolder;

//保存用フォルダがすでにあれば取得、なければ新規作成
ExistenceCheckResult exist = await DCIMFolder.CheckExistsAsync(saveFolderName);
if (exist == ExistenceCheckResult.FolderExists)
{
    saveFolder = await DCIMFolder.GetFolderAsync(saveFolderName);
}
else
{
    saveFolder = await DCIMFolder.CreateFolderAsync(saveFolderName, CreationCollisionOption.ReplaceExisting);
}

//保存するファイルを新規作成
IFile file = await saveFolder.CreateFileAsync(saveFileName, CreationCollisionOption.ReplaceExisting);

//ファイルに画像や動画のByte情報書き込み
using (System.IO.Stream stream = await file.OpenAsync(PCLStorage.FileAccess.ReadAndWrite))
{
    stream.Write(imageData, 0, imageData.Length);
}

DCIMパスの取得はXamarin.Android側で行う必要があります。前回も触れたWebViewRendererといったカスタムレンダラでパスを取得し、連携したXamarin.Forms側のクラス(WebView)に渡せば、大丈夫です。

//Xamarin.AndroidでのDCIMパスの取得
string path = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, "DCIM");

保存後のアルバムへの反映

(なんかのサイトを調べて、知ったはずなのですが、どのサイトか忘れてしまいました...)

ただ保存しただけだと、メディアに保存した画像が表示されません。保存した画像をメディアに通知しないと、Androidを再起動するまではメディアでは表示されません。

この通知はXamarin.Androidのカスタムレンダラ(WebViewRendererなど)のコンストラクタで渡されるContextから行うことができます。

Xamarin.Formsで呼び出すならば、以下を呼び出すActionWebViewRendererで作っておき、WebViewRendererと連携しているWebViewに作ったActionを渡すようにしましょう。

//_contextというメンバ変数をWebViewRendererに用意しておき、コンストラクタで引数のContextを代入しておく。

//imagePathは、「DCIMフォルダへのメディアの保存」のプログラムにあるIFile型のfileからfile.Pathで取得できる。

_context.SendBroadcast(new Intent(Intent.ActionMediaScannerScanFile, Android.Net.Uri.Parse("file://" + imagePath)));

まとめ

PCLStorageを使えば、ファイル保存処理は共通処理としてXamarin.Formsに書けますが、メディア周りに関しても、少し複雑なことをしようとすると、すぐXamarin.Androidなどで各プラットフォームごとに処理を書かないといけないみたいです。

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

【.NET・Visual studio】簡単にwindow表示・アプリケーション開発

❏対応pc・os

  • windows7~10
  • mac os
  • linuxは対応してません。

❏環境

  • windows10

インストールサイト
https://visualstudio.microsoft.com/ja/downloads/

❏早速やろう

コメント 2020-06-25 155302.png
新しいプロジェクトの作成をクリックして、

コメント 2020-06-26 162737.png
wpfと検索しwpf app (.NET Core)を選択します。

コメント 2020-06-25 190356.png
プロジェクト名を入力します。

そうすると以下のような画面が表示されます。
コメント 2020-06-25 174927.png
これを実行します。
上のファイル 編集 表示 プロジェクト……となっている部分があります。そこのデバッグ>デバッグなしで実行をクリックします。

実行結果

すると何も書かれてないwindowが表示されます。
コメント 2020-06-25 175733.png
ではそこにボタンを追加します。
windowを閉じて左よこのツールボックス>すべてのwpfコントール>buttonをクリックして
windowの好きな所にクリックします。するとボタンが出てきます。大きくします。
ツールボックスには他にもカレンダーやパスワードボックスなど沢山あるのでいじってみてください。
コメント 2020-06-25 180914.png

ボタンをダブルクリックするとボタンのプログラムが表示されます。
コメント 2020-06-26 163509.png

private void button_Click(object sender, RoutedE...{}となってる所の{の中に以下のように入力します。

button
MessageBox.Show(("ここに表示したい文字列を入力します。"));//ポップアップwindowを表示する

実行結果

ボタンをクリックするとこうなります。

コメント 2020-06-25 181735.png

❏挑戦

これであるコードを作っていきます。

tyousenn
int a=100;
            if (a == 100)
            {
                MessageBox.Show(("aの値は100です。"));
            } else { 

                MessageBox.Show(("aの値は100ではありません。"));
            }

コメント 2020-06-25 183656.png

実行してボタンをクリックするとこうなります。

コメント 2020-06-25 183847.png

変数aの値を変えると実行結果も異なります。

❏裏技

自分のユーザーフォルダーに移動して⇛source⇛repos⇛自分のプロジェクトの名前がついたフォルダー⇛自分のプロジェクトの名前がついたフォルダー⇛bin⇛Debug⇛netcoreapp3.1⇛自分のプロジェクトの名前がついた実行ファイルをクリックすると自分のプロジェクトが実行されます。

 

❏おわりに

この記事は私が初めて投稿した記事です。
この記事を参考にしてリンクしてくれると嬉しいです。(まあ誰もしないと思うけど。(テ‐テ))

❏参考記事

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

[Unity]マウスカーソルのサイズ変更 C# Windows実行環境

最初に

 ・Unityのバージョンは[2019.4.0f1]です!
 ・Windows環境のみです。

やってること

 ・画像を縮小拡大してマウスカーソルにあげるだけです。

スクリプト

GameSetting.cs
using UnityEngine;
using UnityEngine.AddressableAssets;

public class CursorManager : MonoBehaviour {
    public Texture2D sprite;
    public CursorMode cursorMode = CursorMode.ForceSoftware;
    public Vector2 hotSpot = Vector2.zero;
    public int size;
    private void Update() {
        if(Input.GetKeyDown(KeyCode.D)) {
            Cursor.SetCursor(ResizeTexture(sprite,size,size)), hotSpot, cursorMode);
        }
    }
    static Texture2D ResizeTexture(Texture2D srcTexture, int newWidth, int newHeight) {
        var resizedTexture = new Texture2D(newWidth, newHeight, TextureFormat.RGBA32, false);
        Graphics.ConvertTexture(srcTexture, resizedTexture);
        return resizedTexture;
    }
}

(使い方)上記のスクリプトを適当な汎用Objにアタッチします。

なんやかんやセット
無題.png

結果

Dキーをプッシュ
無題.png

※何度も変更していると画像がぼやけるのでソースを修正。

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

【初心者用】Chromium版MicrosoftEdgeを動かしてみた

はじめに

Chronium版のEdgeを動かす資料があまりなかったので、記事を作成してみました。
サイトを開き、ログインするまで自動で動くシステムをC#で作成します。

ご参考になれば幸いです。

手順

1.Chromium版MicrosoftEdgeの取得
2.WebDriverの取得
3.パッケージの取得
4.コード記述
5.参考文献
6.最後に

1.Chromium版MicrosoftEdgeの取得

Chromium版MicrosoftEdgeをお持ちでないかたは、こちらから入手してください。
コメント 2020-07-03 160109.jpg

2.WebDriverの取得

こちらから入手してください。
※Edgeのバージョンに対応したドライバを入手してください。
edge.PNG
edge2.PNG

3.パッケージの取得

・VisualStudio2019 起動
・ソリューションエクスプローラの該当プロジェクトを右クリック→NuGet パッケージの管理をクリック
・以下画像の参照をインストールする
コメント 2020-07-03 152537.PNG.jpg
※プレリリースにチェックをつけること

4.コード記述

private void OpenEdge()
{
    try
    {
        // ドライバー起動時に表示されるコンソール画面を非表示にする
        var service = EdgeDriverService.CreateChromiumService();
        service.HideCommandPromptWindow = true;

        // EdgeChromium版を使用
        var options = new EdgeOptions;
        options.UseChromium = true;

        var driver = new EdgeDriver(service, options);

        // サイトを開く
        driver.Navigate().GoToUrl("https://aaaa");

        //ユーザーID
        driver.FindElement(By.Name("pid")).SendKeys("userId");
        //パスワード
        driver.FindElement(By.Name("password")).SendKeys("pw");

        //ログインボタン
        IWebElement findbuttom = driver.FindElement(By.Name("NAME_DUMMY04"));
        //ログインボタンをクリック
        findbuttom.Click();
    }
    catch(Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
}

5.参考文献

Selenium4のEdgeDriverでChromium版MicrosoftEdgeを動かす
※とっっっっっても参考になりました!大感謝です!

6.最後に

PythonやPowerShellでChromeを動かす方法はたくさん情報があったのですが、Edge はなかなか資料が見つからず苦労しました。。旧Edge版とChronium版で使用するメソッドが違うのでお気をつけください。

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

【初心者用】C#でChromium版MicrosoftEdgeを動かしてみた

はじめに

Chronium版のEdgeを動かす資料があまりなかったので、記事を作成してみました。
サイトを開き、ログインするまで自動で動くシステムをC#で作成します。

ご参考になれば幸いです。

手順

1.Chromium版MicrosoftEdgeの取得
2.WebDriverの取得
3.パッケージの取得
4.コード記述
5.参考文献
6.最後に

1.Chromium版MicrosoftEdgeの取得

Chromium版MicrosoftEdgeをお持ちでないかたは、こちらから入手してください。
コメント 2020-07-03 160109.jpg

2.WebDriverの取得

こちらから入手してください。
※Edgeのバージョンに対応したドライバを入手してください。
edge.PNG
edge2.PNG

3.パッケージの取得

・VisualStudio2019 起動
・ソリューションエクスプローラの該当プロジェクトを右クリック→NuGet パッケージの管理をクリック
・以下画像の参照をインストールする
コメント 2020-07-03 152537.PNG.jpg
※プレリリースにチェックをつけること

4.コード記述

private void OpenEdge()
{
    try
    {
        // ドライバー起動時に表示されるコンソール画面を非表示にする
        var service = EdgeDriverService.CreateChromiumService();
        service.HideCommandPromptWindow = true;

        // EdgeChromium版を使用
        var options = new EdgeOptions;
        options.UseChromium = true;

        var driver = new EdgeDriver(service, options);

        // サイトを開く
        driver.Navigate().GoToUrl("https://aaaa");

        //ユーザーID
        driver.FindElement(By.Name("pid")).SendKeys("userId");
        //パスワード
        driver.FindElement(By.Name("password")).SendKeys("pw");

        //ログインボタン
        IWebElement findbuttom = driver.FindElement(By.Name("btnname"));
        //ログインボタンをクリック
        findbuttom.Click();
    }
    catch(Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
}

5.参考文献

Selenium4のEdgeDriverでChromium版MicrosoftEdgeを動かす
※とっっっっっても参考になりました!大感謝です!

6.最後に

PythonやPowerShellでChromeを動かす方法はたくさん情報があったのですが、Edge はなかなか資料が見つからず苦労しました。。また旧Edge版とChronium版で使用するドライバ、メソッドが違うのでお気をつけください。

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

【C#】シェーカーソートとは(コメント付き)

シェーカーソートとは?

バブルソートを、効率がよくなるように改良したもの。別名は、双方向バブルソート。
場合によっては普通のバブルソートより遅くなります。

アルゴリズム

バブルソートで1回スキャンを行うと、最後の要素1個がスキャン範囲中最大であることが分かり次回のスキャン範囲を1狭めることができる。さらに、このスキャンの最後で連続してm個の要素の交換が行われていなければ、そのm個についてはソート済みであることが分かるので、次回のスキャン範囲をm狭めることができる。この工夫で、後半が殆ど整列済みのデータに対してバブルソートが高速に行えるようになる。by.wiki

実行結果

しぇーかーそーと1.gif

サンプルコード

bool swapFlag = false;
int[] ShakerSort(int[] _array)
{
    while (true)
    {
        swapFlag = false;
        //配列の回数分回す
        for (int i = 0; i < _array.Length-1; i++)
        {
            //比較元より大きければ入れ替え
            if (_array[i] > _array[i + 1])
            {
                int x = _array[i];
                _array[i] = _array[i + 1];
                _array[i + 1] = x;
                swapFlag = true;
            }
        }
        for (int i = _array.Length - 1; i > 0; i--)
        {
            //比較元より大きければ入れ替え
            if (_array[i] < _array[i - 1])
            {
                int x = _array[i];
                _array[i] = _array[i - 1];
                _array[i - 1] = x;
                swapFlag = true;
            }
        }
            //一度も入れ替え処理が通らなければ
        if (swapFlag==false)
        {
            break;
        }
    }
        //Sortした結果を返す
    return _array;
}

まとめ

まだまだ早くなりますがとりあえずこれがシェーカーソートとなります。
時間があり次第これを改善したシェーカーソートを載せます。

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

【C#】シェーカーソートとは(コード付き)

シェーカーソートとは?

バブルソートを、効率がよくなるように改良したもの。別名は、双方向バブルソート。
場合によっては普通のバブルソートより遅くなります。

アルゴリズム

バブルソートで1回スキャンを行うと、最後の要素1個がスキャン範囲中最大であることが分かり次回のスキャン範囲を1狭めることができる。さらに、このスキャンの最後で連続してm個の要素の交換が行われていなければ、そのm個についてはソート済みであることが分かるので、次回のスキャン範囲をm狭めることができる。この工夫で、後半が殆ど整列済みのデータに対してバブルソートが高速に行えるようになる。by.wiki

実行結果

しぇーかーそーと1.gif

サンプルコード

bool swapFlag = false;
int[] ShakerSort(int[] _array)
{
    while (true)
    {
        swapFlag = false;
        //配列の回数分回す
        for (int i = 0; i < _array.Length-1; i++)
        {
            //比較元より大きければ入れ替え
            if (_array[i] > _array[i + 1])
            {
                int x = _array[i];
                _array[i] = _array[i + 1];
                _array[i + 1] = x;
                swapFlag = true;
            }
        }
        for (int i = _array.Length - 1; i > 0; i--)
        {
            //比較元より大きければ入れ替え
            if (_array[i] < _array[i - 1])
            {
                int x = _array[i];
                _array[i] = _array[i - 1];
                _array[i - 1] = x;
                swapFlag = true;
            }
        }
            //一度も入れ替え処理が通らなければ
        if (swapFlag==false)
        {
            break;
        }
    }
        //Sortした結果を返す
    return _array;
}

まとめ

まだまだ早くなりますがとりあえずこれがシェーカーソートとなります。
時間があり次第これを改善したシェーカーソートを載せます。

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

[C#]Visual Studio 2019 のコードメトリクス分析機能を使ってみよう

日曜日に腹痛で病院にいったら、入院になり、翌日盲腸と診断されてそのまま手術して先日退院してきました。
まだ、おなかの傷が痛むので今週は養生しようと思うので手を動かすのではなくツールを使ってみた!って感じの記事を書いてみたいと思います。

Visual Studio 2019 にはコードメトリクスの分析機能があります。

コードメトリックスの値

こんな感じで分析メニューからいけます。

image.png

丁度、先日リリースされた Covid-19 Radar のプロジェクトが手元にあったので分析にかけてみました。
実行すると以下のような結果が表示されます。

image.png

このウィンドウ上部にあるエクセル風のアイコンをクリックするとみんな大好き Excel で結果が開かれます。あとは好きなようにソートしたりフィルターしたりできます。

このプロジェクトの全体のコードを見たわけではないですが、メソッドだけに絞ってサイクロマティック複雑度でソートしたら以下のような雰囲気になりました。

image.png

これはサーバーサイドのコードですがメソッド単位でのサイクロマティック複雑度の最大値が 13 というのは、私が昔仕事などでコードの分析する際に提出されてきたコードでは出会ったことがありませんでした。
50 超えなんて珍しくもなく数百になるものも中にはあったりすることもあるので、このコードはサイクロマティック複雑度的にはかなり綺麗に整理されたコードなんだなぁというのがわかります。

クライアント側のコードのサイクロマティック複雑度も同じようにメソッドのみに絞り込んで表示してみたら最大のもので 18 でした。

image.png

Exposure Notification の API をスマホで有効化するという処理と通知などの処理が主で複雑な Bluetooth LE の電波強度から接触したかどうかという処理がアプリ内に不要なので、割とさっくりと作られているように感じます。

実際に数コミットくらいは私の名前もコミットログにあるのですが、触ってみるときにメソッドが短いので読みやすいコードだなぁと思いました。なので、 C# をやったことがある人には巨大なメソッドがないメソッド単位で見た限りだと綺麗に分割されていそうなプロジェクトを読みたいというのであれば Covid-19 Radar のコードの Azure Functions と Xamarin のコードをクローンしてみてみてみるというのは、もしかしたらアリなのではと思いました。

サイクロマティック複雑度

ここまで散々言ってきたサイクロマティック複雑度ですが、循環的複雑度という名前でも使われていて(私のまわりはサイクロマティック複雑度でした、まぁ英語をそのままカタカナにするか漢字にするかの違いです)

端的に言うとソースコード内の処理の経路の数。つまりこのメソッドの全てのパターンをテストしようとすると最低でも、この数字と同じ数だけのテストケースを実施しないと出来ないというものです。

個人的な所感では、これが 20 を超えてくると危険です。まぁでもエンタープライズアプリケーション開発の現場では数百とかいうのがざらにいるので感覚麻痺しちゃうけど麻痺したら危険信号なので気を付けましょう。

こういったソースコードの各種メトリクスは、CI/CD パイプラインで継続的に計測しておくとソースコードを俯瞰する立場の人からしたらありがたいです。
有名どころだと SonarQube を使ってるところが多いんじゃないかと思います。Qiita にもいくつか記事があるので気になったら見てみるといいと思います。

昨日に比べてなんか複雑度の多いコードがバカみたいに増えてるんだけど!?っていうのに気づいたときに対処できるので個人的には好きです。

まとめ

Covid-19 Radar のコードはメソッド単位(クラス間の依存関係とかは今回は見てないのでそこは知らない)の複雑度で見ると凄く整理されてメソッド分割されているように見えました。実際に私も個人的にコードを見たり少しコミットするために見たときには「何だこのメソッド!?複雑でよくわからないから近づかんとこ…」というのはありませんでした。

サーバーサイドのほうについてはユニットテストもあってユニットテストのコードカバレッジを見ると 70% もユニットテストでカバーしているので、本当にお手本みたいな書き方がされているので興味のある人は見てみるといいと思います。

ではでは。

GitHub | Covid-19 Radar

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

未送付のfreee請求書一覧を定期通知する

はじめに

バックオフィス業務として、請求書をお客様に送付するタイミングがあると思います。
freee上で請求書を作成したけど、送付し忘れてた!なんてことを予防するために、未送付の請求書一覧を定期的にslack通知できると便利ではないでしょうか。

というわけで本項では上記の仕組みについて説明したいと思います。
こちらC#を利用して実装しているので、一部C#も含めた説明になります。

未送付のfreee請求書一覧を取得する

freee APIのうち、GET /api/1/invoices を用いることで取得することが出来ます。
https://developer.freee.co.jp/docs/accounting/reference#/Invoices/get_invoices

invoice_status(請求書のステータス)に"unsubmitted"を指定することで、送付待ちの請求書一覧を取得することが可能です。

https://developer.freee.co.jp/release-note/2948
詳細は上記、2-5 請求書ステータスの変更(C10)参照ですが、2020/12のAPI仕様変更に伴い送付待ちの請求書一覧が取得可能になります。
API切り替えまでの間に新API仕様でコールする場合は上記を元にリクエストヘッダを指定する必要がありますのでご注意ください。

// freee-accounting-sdk-csharpを利用した場合の取得
int myCompanyId = 12345678;

string invoiceStatus = "unsubmitted";
string description = null;
string paymentStatus = null;

var invoiceApi = new InvoicesApi(config);

var invoices = await invoiceApi.GetInvoicesAsync(myCompanyId, null, null, null, null, null, null, null, description, invoiceStatus, paymentStatus, null);

freee-accounting-sdk-csharpを用いる場合は上記のような呼び出し方になります。
(2020/07/03時点では、先述した新API仕様に対応していないので、利用の際はSDKに手を加えましょう)

未送付の請求書一覧の中で、一定日数経過した請求書を取得する(絞り込む)

GET /api/1/invoicesのstart_issue_date/end_issue_dateを指定すれば取れそうですが、自分の指定の仕方がまずいのか意図したとおり取れなかったので、自前でチェックします。
start_issue_dateには請求日がyyyy-MM-ddのフォーマットで入っています。

こちらの日付と現在日付を比較することによって、経過日数を取得することが可能です。

// JSTの現在日付の取得
TimeZoneInfo jstZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
DateTime jstDate = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, jstZone).Date;
// 時間差分の算出
DateTime issueJstDate = DateTime.ParseExact(invoice.IssueDate, "yyyy-MM-dd", null);
TimeSpan elapsedTime = jstDate - issueJstDate;

このあと、例えばelapsedTime.TotalDays >= 1のようにすれば1日以上経過した請求書であることが判定できます。

通知の仕組み

弊社ではslackを通常利用していることもあり、通知はslackに行うことにします。

Incoming Webhookを利用します。

通知先のテキストに、https://secure.freee.co.jp/docs_v2/invoice/{invoice.Id}を含めることによって、slackからすぐに請求書へ飛べるので便利かなーと思います。

定期的に実行するための仕組み

上記で用意した処理を定期的に呼ぶための仕掛けが必要になります。
ここは何でもよいですが、Azure Functionsのタイマ関数を利用しています。

[FunctionName("invoiceNotification")]
public async void Run([TimerTrigger("0 30 12 * * 1-5")] TimerInfo myTimer, ILogger log)
{
  (上記で説明した処理を呼び出す)
}

この例では、0 30 12 * * 1-5とトリガが設定されていますが、月-金12:30に処理が呼び出されます。
なので、午後に未送付の請求書を送ってしまおう!という感じの運用ですね。

こんな感じで毎日(月-金)通知されます

notificate.png

その他

特定の請求書に絞り込みたい場合は、請求書の概要(description)に値を入れておいて、請求書検索時はdescriptionを指定すると絞り込むことが可能です。
複数の業務の請求書を扱っている場合は、そちらを用いて請求書を分類することで、その請求書に関係するメンバーのみjoinしているslack chに通知するといったことも出来るので、そちらが望ましいでしょう。

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

【C#】バブルソートとは(コード付き)

バブルソート(BubbleSort)とは?

バブルソートとは、与えられたデータを大小などの順序通りになるよう並べ替えるソーアルゴリズムの最も基本的な手法の一つです、端から順番に隣接する要素同士を比較・交換していくもの。

どんな動きをするの?

Sort.gif

サンプルコード

    int[] BubbleSort(int[] _array)
    {
        //配列の回数分回す
        for (int i = 0; i < _array.Length; i++)
        {
            //配列の回数分回す
            for (int j = 0; j < _array.Length; j++)
            {
                //比較元より大きければ入れ替え
                if (_array[i] < _array[j])
                {
                    int x = _array[j];
                    _array[j] = _array[i];
                    _array[i] = x;
                }
            }
        }

        //Sortした結果を返す
        return _array;
    }

豆知識

実はこのバブルソートですがある一文字を変えるだけで劇的に入れかえ回数が変化します。

バブルソート改

for (int j = 0; j < _array.Length; j++)

この部分を

for (int j = i; j < _array.Length; j++)

とするだけですごく変わります!

実行結果

Sort1.gif

サンプルコード

    int[] BubbleSort(int[] _array)
    {
        //配列の回数分回す
        for (int i = 0; i < _array.Length; i++)
        {
            //配列の回数分回す
            for (int j = i; j < _array.Length; j++)
            {
                //比較元より大きければ入れ替え
                if (_array[i] < _array[j])
                {
                    int x = _array[j];
                    _array[j] = _array[i];
                    _array[i] = x;
                }
            }
        }

        //Sortした結果を返す
        return _array;
    }

まとめ

たった一か所変更しただけで半分近くに比較する回数を減らすことができました。
みなさんがバブルソートを使う場合このあたりを気を付けましょう。

追記

コメントにてもっと早くなる方法を記載してくださった方がいらっしゃいましたのでそちらの実行結果とソースをのせます!

 int[] BubbleSort(int[] _array)
    {
        //配列の回数分回す
        for (int i = 0; i < _array.Length; i++)
        {
            //配列の回数分回す
            for (int j = i+1; j < _array.Length; j++)
            {
                //比較元より大きければ入れ替え
                if (_array[i] < _array[j])
                {
                    int x = _array[j];
                    _array[j] = _array[i];
                    _array[i] = x;
                }
            }
        }

        //Sortした結果を返す
        return _array;
    }

Sort2.gif

まとめ2

少し早くなっていますね!
ソートってこういう少しの変更でも工程数が変化するのがいいですよね。

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

C#でビルドタスクが書けるnukebuildについて

はじめに

以前CakeBuildについて記事を書いたが、同種のフレームワークとしてnukebuildというものを試したので、とりあえず入門的な記事を書く。

なお、本当の名前は"nuke"だが、同音異義語が世に溢れているため、ここでは"nukebuild"と呼称する(dotnet nukeも既に存在してるし)。

これは何か?

CakeBuildのように、主にビルド用途に最適化された、指定したタスクを実行してくれるもの。
nukebuildの本体はdotnetのライブラリであり、最終的にタスクを実行するのは普通のコンソールアプリである。
つまり、特殊なプラグイン等を必要とせずに、既存のdotnet系に対する言語サポート(デバッグ、補完、nugetパッケージ)が受けられるということになる。
ただし、タスクの実行部分は専用のプラグインが存在する。

最初の実行

まずは何もないディレクトリに移動し、下記を実行してみる

  1. dotnet tool install -g Nuke.GlobalTool
    • "nuke"というコマンドが使えるようになる(使えない場合は、PATHに$HOME/.dotnet/toolsを追加する)
  2. nuke
    • 下記のような質問を聞かれるので、答えていく
      1. Could not find .nuke file. Do you want to setup a build? [y/n]
        • ひな形を作るかの確認。yを入力してエンターして次へ
      2. Where should the build project be located?
        • どこにビルドタスクを記述するプロジェクトを作成するか。デフォルトではbuild以下に作られる
      3. Which NUKE version should be used?
        • 使用するnukebuildライブラリのバージョンを決める
        • 特に何もなければ安定最新版を選ぶ
      4. Which solution should be the default?
        • タスク内で参照できるソリューションファイルがどこにあるか
        • 存在しなければ"None"で問題ない
  3. nuke
    • タスクが実行される
    • windowsで文字化けする場合は、ターミナルのコードページを"65001"に変更して実行する(出力にユニコード文字を使用しているため)

ひな形を作るかどうかは、カレントディレクトリに.nukebuild.ps1またはbuild.shいうファイルがあるかどうかで確認しているので、あれば質問は聞かれず、タスクを実行しようとする。

Nuke.GlobalTool

nukebuildの実行を補助する dotnet global tool
機能としては、

  • テンプレートプロジェクトとブートストラッパーの作成
  • タスクの実行を行う
    • カレントディレクトリと上位ディレクトリを順番に見てbuild.ps1を探し、それを実行している

実はひな形さえ作れば以後は無くても良い

構成ファイル

ひな形作成で作られるファイルには以下のようなものがある。

  • .nuke
    • nukebuildを実行する時のルートディレクトリに置く
    • nukebuildはこのファイルがある場所をルートディレクトリと判断する
  • build.ps1
    • タスクを実行するためのブートストラッパー
    • dotnet sdkが無い場合はダウンロードを行って、そこからタスクを実行しようとする
    • ひな形作成時に、タスクプロジェクトのcsprojへのパスがハードコードされるので、構成を変えるときは注意
    • 動作的には以下の事をしている
      1. dotnet sdkが無ければDLして.tmpに展開
      2. ハードコードされたcsprojをビルド
      3. ビルドされたプロジェクトを実行
  • build/*
    • タスクプロジェクト(デフォルトだとこのパス)
    • 中身はNuke.Commonを参照している
    • Nuke.Common.NukeBuildを継承したクラスBuildが作られているので、このクラス内にタスクを記述していく
    • 一つのクラスにメンバーを追加していくことになるので、大規模になるならばpartialクラスにした方が良い

タスクの記述

タスクはタスクプロジェクトのBuildクラス内に、Nuke.Common.Targetのインスタンスを追加していって記述する。
このTargetは定義としては public delegate ITargetDefinition Target(ITargetDefinition definition)というデリゲートなので、以下のようにTargetのインスタンスを記述する。

Build_MyTask.cs
// 大元のBuildクラスをpartialクラスにしている前提
using Nuke.Common;
partial class Build
{
    // Executesの中の処理を実行する
    // Executesは必須ではなく、何もしないタスクを作ることも可能
    Target MyTask => _ => _
        .Executes(() => Logger.Info("MyTask"));
    // 分解すると、以下のような意味になる
    Target MyTask2
    {
        get
        {
            Target ret = (ITargetDefinition t) =>
            { return t.Executes(() => Logger.Info("MyTask")); };
            return ret;
        }
    }
}

タスクの実行

タスクを記述した後、ブートストラッパー、あるいはビルドプロジェクトを実行すれば、タスクが実行される。デフォルトでは、Main関数に記述されているFuncで指定したタスクが実行される。

ヘルプを表示させたい場合は、コマンドライン引数に--helpを指定すればOK
ターゲットを指定する場合は、--target [TargetA] [TargetB]のように指定する。
また、そのほかにも独自の引数を指定することができるが、それは後述する

タスクの依存関係

例えば"Build"の前には"Restore"を成功させてほしい、"Publish"は"Build"が実行されていなくてもいいけど、必ず後に実行したい、等、タスクの実行順序と依存関係を制御したい場合は、Targetの定義時にITargetDefinitionで以下の操作を行う

ITargetDefinition DependsOn(params Target[] t)

指定したタスクを依存しているタスクとして登録する。こうすると、"t"が事前に実行されるようになり、かつ成功しない限り、そのタスクは実行されなくなる。

DependsOn.cs
Target TaskA => _ => _.Executes(() => Logger.Info("TaskA"));
Target TaskB => _ => _.DependsOn(TaskA).Executes(() => Logger.Info("TaskB"));

この状態でTaskBを実行しようとすると、かならずその前にTaskAが実行されるようになる。

ITargetDefinition DependentFor(params Target[] t)

DependsOn()とは逆に、指定したタスクが自分に依存しているという登録を行う。

DependentFor.cs
Target TaskA => _ => _.Executes(() => Logger.Info("TaskA"));
Target TaskB => _ => _.DependentFor(TaskA).Executes(() => Logger.Info("TaskB"));

この状態でTaskBを実行してもTaskAは実行されないが、TaskAを実行しようとすると、必ずその前にTaskBが実行されるようになる

ITargetDefinition Before(params Target[] t)

指定したタスクに対して、依存関係は作成されないが、必ず引数指定したタスクよりも前に実行されることが保証されるようになる

Before.cs
Target TaskA => _ => _.Executes(() => Logger.Info("TaskA"));
Target TaskB => _ => _.Before(TaskA).Executes(() => Logger.Info("TaskB"));

この状態でTaskAを単独で実行してもTaskBは実行されないが、TaskAとTaskBを同時に実行しようとすると、必ずTaskBが先に実行されるようになる

ITargetDefinition After(params Target[] t)

Before()とは逆に、指定したタスクに対して、依存関係は作成されないが、必ず引数指定したタスクよりも後に実行されることが保証されるようになる

Before.cs
Target TaskA => _ => _.Executes(() => Logger.Info("TaskA"));
Target TaskB => _ => _.After(TaskB).Executes(() => Logger.Info("TaskB"));

この状態でTaskAを単独で実行してもTaskBは実行されないが、TaskAとTaskBを同時に実行しようとすると、必ずTaskAが先に実行されるようになる

必要条件の記述

あるタスクを実行する時、条件を満たしていないとエラーとして扱いたい場合、ITargetDefinition Requires(params Expression<Func<bool>>[] conditions)が使える。
引数に指定した条件式はタスク実行前に評価され、falseならばエラーとして処理される

Requires.cs
[Parameter("Hoge")]
readonly string Hoge;
Target TaskA => _ => _.Requires(() => !string.IsNullOrEmpty(Hoge));

上記でパラメーター無しでTaskAを実行しようとすると、Hogeが空なのでエラーで終わる。

特定の条件の時のみの実行

ある条件を満たした時のみタスクを実行したい場合、ITargetDefinition OnlyWhenStatic(params Expression<Func<bool>>[] conditions)またはITargetDefinition OnlyWhenStatic(params Expression<Func<bool>>[] conditions)が使用できる。

見た目はほぼ同じだが、OnlyWhenDynamicは、該当タスク実行直前に評価され、OnlyWhenStaticは、全てのタスク実行の前に一回だけ評価される。

OnlyWhen.cs
[Parameter("Hoge")]
string Hoge;
Target TaskA => _ => _.OnlyWhenDynamic(() => !string.IsNullOrEmpty(Hoge));
Target TaskB => _ => _.OnlyWhenStatic(() => !string.IsNullOrEmpty(Hoge));

上記でパラメーター無しでTaskBを実行しようとすると、TaskBの実行はスキップされる。
TaskAの場合は、TaskAより前に実行される他のタスクで値が設定された場合、実行はスキップされない。

イベントのフック

初期化やクリーンアップ等、ターゲット実行の前や後に処理を割り込ませたい場合は、イベントのフック機能を使う。
基本的にBuildクラスの中で各種イベントをオーバーライドすればOK。
実際よく使うのは、OnBuildInitialized(コマンドライン引数の解釈、各種タスクのセットアップが完了した後に一回実行される)、OnBuildFinished(全ての処理の後に実行される)だと思う。

Event.cs
protected override void OnBuildInitialized()
{
    Logger.Info("on build initialized");
}
protected override void OnBuildFinished()
{
    Logger.Info("on build finished");
}

パラメーターの追加

コマンドライン引数にはデフォルトで指定できるもの(--target,--help等)があるが、さらに追加で指定することも可能。
やり方としては、Buildクラスのメンバーに、Nuke.Common.ParameterAttributeを持ったプロパティまたはフィールドを追加すること。
ひな形にも記述があるが、以下のようにする。

Parameter.cs
[Parameter("--helpで表示される文章の記述")]
readonly string MyParameter1 = "デフォルト値"; // デフォルト値が無ければdefault(T)が使われる

サポートされる型は、デフォルトではstring、プリミティブ型と、その配列型、nullable型をカバーしている。独自型のサポートは後述。
パラメーターの名前は、基本的にプロパティ/フィールド名をもとに決定されるが、CamelCaseを名前に指定した場合は、kebab-caseに自動的に変換される。
つまり、上記の場合は、パラメーター名が--my-parameter1となる。
なお、大文字小文字は区別しない。

配列を渡したい場合は、受け取るパラメーター型を配列型にして、該当引数の後に複数の値を渡せばOK。
nukebuildでは、次のパラメーター指定が始まるまで、以降の引数全てをパラメーターの引数と解釈するようになっている。

例えば [Parameter("")]readonly int[] MyParameter1 = new int[0];のように指定して、--my-parameter1 1 2 3のように指定すると、MyParameter1には1,2,3の値が入る

独自パラメーター型の追加

パラメーターに独自の型をサポートしたい場合、その型をSystem.ComponentModel.TypeConverterを使って、stringから型変換可能にしておく必要がある。

独自型の宣言では以下のように実装する

TypeConverter.cs
using System.ComponentModel;
using System;
// この属性を追加して、型変換の時は指定のTypeConverterを使うようにする
[TypeConverter(typeof(MyParameterType.MyTypeConverter))]
class MyParameterType
{
    // System.ComponentModel.TypeConverterを継承したクラスを作成する
    class MyTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            // string型の変換を受け付けられるようにしておく
            if (sourceType == typeof(string))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            // 実際の変換処理
            var str = (string)value;
            var ret = new MyParameterType();
            ret.Hoge = str;
            return ret;
        }
    }
    public string Hoge;
}

後は、パラメーターの型に指定した値を入れるだけでOK。

ファイルパスの構築

ファイルパスの構築は、AbolutePathまたはRelativePathを使用する。
どちらも'/'をオーバーロードしているため、'/'で繋げてパス構築が可能。
文字列に出力する際に、プラットフォーム固有の区切り文字へ変更してくれる。

// 使用する際はnewではなくstringからのキャストを使用する
var rootpath = (AbsolutePath)"c:/";
var path_a = rootpath / "a"; // c:\a
var path_b = path_a / "b"; // c:\a\b

デフォルトでは、AbsolutePath RootDirectoryというプロパティがBuildクラスで使用できるので、各種タスクの起点をここに設定するといいと思う。

よく使う機能

FileSystemTasks

ファイルコピー、作成、移動のユーティリティ。
なお、ファイル検索に関してはPathConstructionのGlobFiles等を使用する。

ProcessTasks

プロセス起動等のユーティリティ。
ToolSettingsは直接インスタンス化できないので、基本的には引数が沢山あるバージョンを使うことになる。
実行時のワーキングディレクトリはRootDirectoryと一致しない場合があるので、workingDirectoryの指定はしておいた方が良い。

MSBuildTasks

MSBuildを実行するための補助を行う。ここで使用するMSBuildは、VisualStudioあるいはmonoが持つものであり、dotnet sdkとは異なるものになることに注意。
実行パターンは複数あるが、最もよく使うのはMSBuild(Configure<MSBuildSettings> configure)だろうか。
Configure<MSBuildSettings>とは、要するにFunc<MSBuildSettings, MSBuildSettings>のようなものである。
とりあえずSetProjectFile(string)は必須で、さらに使用するVSのバージョンを限定したい場合は、SetMSBuildVersion()、ビルドアーキテクチャを指定したい場合は、SetTargetPlatform()を実行することになる。

CompressionTasks

zip,tar,gz,bz2等、圧縮・伸長等を行うユーティリティ。
xzや7zに関してはサポートしていないので、別途処理を用意する必要がある。

終わりに

とりあえずここまで書けば、基本的なタスクというのは書けると思う。
公式ドキュメントを参考にしながら記述すること。ここで紹介していない便利機能もあるので、
一回は見てみると良い。

本当はcakebuildとの比較を書きたかったが、この記事が長くなってしまったので宿題とする。

参考リンク

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