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

【第1回】C#でWEBアプリ【ASP.NET Core】

はじめに

最近気になるとあるSaaS企業の製品がC#で出来ているとのことだったのでチュートリアルを参考に触ってみる。C#はWindows Formアプリを作った経験はありますが、WEBアプリについては未知の領域です。(そもそもC#でWEBアプリを作れると思っていなかった・・・)

以下を参照して進めていきます。
チュートリアル: ASP.NET Core で Razor ページ Web アプリを作成する

インストール

まずはVisual Studioをインストールします。
以下のサイトからお使いのOSに合わせてダウンロード・インストールします。

・Visual Studio

プロジェクトの作成

[新規]→[.NET Core]→[アプリ]→[Webアプリケーション]

スクリーンショット 2020-03-16 21.51.52.png

任意のプロジェクト名を入力します。
今回は「todo」とします。

スクリーンショット 2020-03-16 21.53.04.png

※キャプチャは「ToDo」になってますが以下は「todo」で行っていきます。

作成が完了したらとりあえずアプリの実行をしてみます。
[▶︎]ボタンを押して実行します。
初回は開発証明書のインストールを求められることがあります。その場合は画面にしたがってインストールします。
開発証明書について

スクリーンショット 2020-03-16 22.07.34.png

ビルドに成功するとlocalhost:5001でアプリが実行されます。

スクリーンショット 2020-03-16 22.09.24.png

簡単にアプリが立ち上がりました。

各フォルダの説明

ひとまずアプリが実行出来たところでフォルダがどのように構成されているか見てみます。

Pages

PagesフォルダにはRazorページとそのサポートファイルが格納されます。Razor(レイザー)とはASP.NET の枠組みで提供している、 ビューエンジンのことみたいです。ビューエンジンというのは、MVC (モデル・ビュー・コントローラ) でいうところの、 ビューの部分を担当する仕組みなわけですが、要は HTML をどう表示するか、というところを受け持つエンジンということです。

参考
・Razorビューエンジン

wwwroot

HTMLファイル、JavaScriptファイル、CSSファイルなどの静的ファイルが格納されます。

appSettings.json

接続文字列などの構成データが保存されます。

Program.cs

プログラムのエントリ ポイントが保存されます。

Startup.cs

アプリの動作を構成するコードが含まれています。

データモデルの追加

続いてデータモデルを追加していきます。このアプリのモデルクラスは、データベースを操作する際、Entity Framework Core(EF Core) (SQLite EF Core データベースプロバイダー)が使用されます。EF Coreは、データアクセスを簡略化するORマッパーのことです。

それではtodoフォルダの配下にModelsフォルダを作成します。

スクリーンショット 2020-03-16 22.34.39.png

スクリーンショット 2020-03-16 22.34.52.png

次のModelsフォルダにItemというクラスを作成します。

スクリーンショット 2020-03-16 22.36.17.png

スクリーンショット 2020-03-16 22.37.52.png

Item.cs
using System;
namespace todo.Models
{
    public class Item
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
    }
}

IDフィールドはデータベースの主キーとして必要です。
今回はタイトルと説明というフィールドを用意しました。
ここまできたら一度プロジェクトをビルドしてコンパイルエラーがないことを確認します。

モデルのスキャフォールディング

APS.NETではモデルクラスを定義し、スキャフォールディングを行うことで、CRUDに対応する画面とコードを自動生成してくれます。

ASP.NET 4.5の「Scaffolding(スキャフォールディング)」機能を試す(前編)

Pagesフォルダ配下にItemのCRUDに対応する画面定義などを作成する用のItemsというフォルダを作成します。

スクリーンショット 2020-03-16 22.45.41.png

スクリーンショット 2020-03-16 22.48.42.png

[Items]→[追加]→[新しいスキャフォールディング]

スクリーンショット 2020-03-16 22.49.33.png

[Entity Frameworkを使用するRazorページ(CRUD)]を選択

スクリーンショット 2020-03-16 22.50.44.png

使用するモデルクラスは「Item」を選択します。
使用するDbContextクラスには「ItemContext」と入力します。

スクリーンショット 2020-03-16 22.59.00.png

処理が完了するとappsettings.jsonファイルにローカルデータベースへの接続に使用される接続文字列が記載されます。

またtodoフォルダ配下でターミナルを開き以下のコマンドを実行し、.NET Core CLIのEntity Framework Coreツールをインストールします。

dotnet tool install --global dotnet-ef

スキャフォールディングを行ったことで、Itemsフォルダ配下にCRUDに対応する.cshtmlファイルとDataフォルダにコンテキストファイルが作成されています。

スクリーンショット 2020-03-16 23.08.54.png

データベースの作成

todoフォルダ配下でターミナルを開いて、以下のコマンドを実行しデータベースの作成を行います。

dotnet ef migrations add InitialCreate

コマンドはアプリを停止した状態で行いましょう。実行中だとエラーになります。
変更時は以下のコマンドです。

dotnet ef database update

確認

データベースの作成が完了したらアプリを実行し、localhost:5001/Itemsにアクセスします。
スクリーンショット 2020-03-16 23.17.56.png

上記のような画面が表示されれば成功です。この画面はItemsフォルダのindex.cshtmlが表示されています。

次に「Create New」を押下してデータを登録してみます。

スクリーンショット 2020-03-16 23.20.14.png

スクリーンショット 2020-03-16 23.23.46.png

簡単にデータの登録が出来ました。その他、編集や削除も行うことが出来ます。

さいごに

初めてC#でWEBアプリを触ってみましたが、難しいですね。。。.NETはガチガチのフレームワーク感があるので、慣れるまで難しそうです。どこまで柔軟に開発できるかも気になりますね。次回はスキャフォールディングで作成されたファイルの中身をみていきます。(気力あったら更新)

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

やさしいチャットボットのつくりかた -4.Twitterからつぶやきをとってきてみよう!

どういう記事?

Azure素人の自分がMicrosoft AzureのBot FrameworkとCognitive Serviceと触れ合いながら、難易度も人への接し方も“やさしい”チャットボットを作ってみた際の備忘録の第4部です。どんなチャットボットを作るのかを含め、これまでの手順は第1部第2部第3部にございます。
PowerPointで記録していたものをQiita化してみたものになるので、そんな感じの画像が頻出します。
Qiita初心者なので、書き方が下手でも広い心で許してほしいです。そして全体的にゆるいです。

アジェンダ

以下の5部構成になります。この記事は第4部にあたります。

  1. Bot Frameworkでチャットボットを作ってみよう!
  2. ボットをAzure(クラウド)にデプロイしてみよう!
  3. Cognitive Serviceでテキスト分析をしてみよう!
  4. Twitterからつぶやきをとってきてみよう!←イマココ
  5. LINE経由でボットと話してみよう!

なお、公式ドキュメントに沿って作りましたので、それらのリンクを張り付けつつ、辿った道筋通りに書いていきます。
その筋の方からすると遠回りだったり野暮ったかったりすると思いますが、ご容赦ください。

2. Twitterからつぶやきをとってきてみよう!

これまで、第1部で作ったチャットボットのソースコードを、第2部でAzure(クラウド)上へデプロイし、ユーザーから受け取ったコメントをAzure Cognitive ServiceText Analyticsを使って、ネガティブorポジティブの判定をしてみました。今回は、図中の赤枠部分、Text Analyticsの判定結果がネガティブだった場合、Twitterで偉人さんのありがたいお言葉をつぶやく励ましBotさんのつぶやきをランダムに選択し、ユーザーに返してあげる部分を作っていこうと思います。
image.png

2.1 事前準備

Twitter Developer登録

Twitterの各種APIを使うためには、Developer登録が必要となります。審査に数日かかるようです。
こちらにDeveloper登録について、詳しく書かれているので参考にしてください。
手順中で、Access tokenとaccess token secretが生成されますので、忘れずにメモをしておいてください。後々使います。

CoreTweetインストール

C#からTwitterAPI を扱うためのライブラリである、CoreTweetのインストールをします。第3部のTextAnalyticsの設定でも実施したパッケージのインストールをします。検索ボックスでCoreTweetと検索して、インストールを実行します。

image.png

2.2 sentimentAnalysisExampleメソッドから分析スコアを返す

第3部で作成したソースコードを前提に、必要な変更を加えていきます。まずは、ユーザーのメッセージをText Analyticsリソースに投げて分析しているsentimentAnalysisExampleメソッドが、呼び出し元に分析結果のスコアを返すようにコードを変更します。

EchoBot.cs
static double sentimentAnalysisExample(ITextAnalyticsClient client, string message)//戻り値をvoidからdouble型に変更
{
     var result = client.Sentiment(message, "ja");//引数messageを分析対象にし、言語は日本語に設定
     Debug.WriteLine($"User Message: {message}");//引数messageの内容を出力
     Debug.WriteLine($"Sentiment Score1: {result.Score:0.00}");//出力確認1
     return (double)result.Score;//分析スコアを返す
}

呼び出し側でも戻り値を格納する変数を用意する必要がありますので、OnMessageActivityAsync内でsentimentAnalysisExampleの部分に以下の通り変更を加えます。Debug.WriteLineは値の受け渡しが正常にできているかの確認のための出力です。

EchoBot.cs
var score = sentimentAnalysisExample(client,turnContext.Activity.Text);//戻り値を格納するためscoreを用意Debug.WriteLine($"Sentiment Score2: {score:0.00}");//出力確認2

2.3 スコアを元に条件分岐をする

Text Analyticsでは0~1の間の値を返します。1に近いほどポジティブで、0に近いほどネガティブな判定という意味となります。
今回は、スコアが0.5以上の場合はポジティブとみなして塩対応し、0.5未満の場合はTwitterからありがたいお言葉をとってきたいと思いますので、OnMessageActivityAsync内で条件分岐をし、それぞれの場合でDebug.WriteLineで出力確認をしたいと思います。
今までのOnMessageActivityAsyncに対する変更をまとめると以下の通りとなります。

EchoBot.cs
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var replyText = $"Echo: {turnContext.Activity.Text}";
    await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);

    //for TextAnalytics
    var client = authenticateClient();
    var score = sentimentAnalysisExample(client,turnContext.Activity.Text);//戻り値を格納するためscoreを用意
    Debug.WriteLine($"Sentiment Score2: {score:0.00}");//出力確認2

    if(0.5 <= score)
    {
        Debug.WriteLine($"塩対応");//出力確認3
    }
    else
    {
        Debug.WriteLine($"励まし対応");//出力確認4
    }
}

一度実行して試してみましょう。ボットに話しかけたメッセージと、そのスコア(2回)、スコアに応じた対応内容が出力されていればOKです。
image.png

2.4 励まし対応(Twitter連携)部分を作る

ちゃんとスコアを受け取り、条件分岐できていることが確認できたので、いよいよTwitterとの連携部分を作っていきます。事前準備でインストールしたCoreTweetを使うので、以下の通りソースコードの上の方(usingほげほげがあるところ)に追加します。

EchoBot.cs
using CoreTweet;

そしてpublic class EchoBotの中のsentimentAnalysisExampleと同じレベル(深さ)に、Twitterからツイートを取ってくるためのgetTweetメソッドを用意します。

EchoBot.cs
 static string getTweet(double score)
 {
     Random cRandom = new System.Random(); //取得したツイートからランダムに1つ選択するための乱数
     var tokens = Tokens.Create("<API KEY>", "<API SECRET>", "<ACCESS TOKEN>", "<ACCESS TOKEN SECRET>");  //接続用トークン発行
     var tweet = "";//取得したツイートを格納する変数

     var parm = new Dictionary<string, object>();  //条件指定用Dictionary
     parm["count"] = 60;  //取得するツイート数
     parm["screen_name"] = "hagemasi1_bot";  //取得したいユーザーID


     Task task = Task.Factory.StartNew(async () =>
     {
          var tweets = await tokens.Statuses.UserTimelineAsync(parm); //parmの内容に従ってツイートを取得

          var random = cRandom.Next(61); //0~60の間の乱数を生成
          tweet = tweets[random].Text; //取得した60ツイートからrandom番目のツイートを格納

     }).Unwrap();

     task.Wait();

     return tweet; //選んだツイートを戻り値として返す
}

それぞれ簡単に説明していきます。

  • Random cRandom = new System.Random();
    一括でとってきたツイートたちから、どれか1つを選択する際に使う乱数です。

  • var tokens = Tokens.Create()
    Twitterに接続する際のトークンです。それぞれ以下の手順で確認して、正しいものを入れていきます。

Twitter Developerサイトにアクセスし、画面の右上で、自分のTwitterアカウントより、Appsを選択します。
image.png

該当アプリのDetailsボタンをクリックします。
image.png

Keys and tokensタブをクリックします。API keyとAPI secret keyが表示されます。Access tokenとaccess token secretについては、事前準備のTwitter Developer登録でメモしておいたものを使います。もしメモし損ねてしまっていたら、Regenerateボタンで再作成してください。
image.png

  • var tweet = "";
    取得してきたツイート群から、実際にユーザーに返すものを格納する変数です。

  • var parm = new Dictionary();
    取得するツイートの条件を指定するためのDictionaryクラスです。今回は取得してくる全ツイート数(60ツイート)と、取得対象のTwitterアカウントのID(hagemasi1_bot)を指定しています。Dictionaryクラスの公式リファレンスはこちら

  • var tweets = await tokens.Statuses.UserTimelineAsync(parm);
    直前のDictionaryクラスで指定した内容を引数にして、hagemasi1_botのツイートを60ツイート取得してきています。戻り値は取得したツイートたちです。CoreTweetの公式リファレンスはこちら

  • var random = cRandom.Next(61);
    取得した60個のツイートから、何番目のツイートをユーザーに返すか、乱数で決めています。randomには0~60の値のいずれかが入ります。

  • tweet = tweets[random].Text;
    取得した60個のツイートのうち、random番目のものを選んで、tweet変数に格納しています。これをユーザーに返します。

  • return tweet;
    tweet変数を呼び出し元に返します。

Twitterからツイートを取得するメソッドができたので、OnMessageActivityAsync内の条件分岐でDebug.WriteLine($"励まし対応")としていた部分からgetTweetを呼び出して、結果を出力するようにします。

EchoBot.cs
if(0.5 <= score)
{
    Debug.WriteLine($"塩対応");//出力確認3
}
else
{
    var res = getTweet(score);
    Debug.WriteLine($"励まし対応:{res}");//出力確認4
}

ではここでまた、実行してみましょう。できるだけネガティブな発言をボットに投げかけてくださいね。
image.png

ネガティブ判定され、Twitterから偉人さんのありがたいお言葉を取得してくることができました!

2.5 塩対応部分を作る

TextAnalyticsの分析スコアが0.5以上だった場合の、塩対応用メソッドgetShioCommentを、OnMessageActivityAsync内、getTweetと同じレベル(深さ)に作っていきます。こちらは特に何かと連携はせずに、コード内に書いたいくつかの塩対応フレーズからランダムに選んで返すだけのものにします。励まし対応の部分と被る要素が多いので、説明は割愛します。

EchoBot.cs
static string getShioComment()
{
   Random cRandom = new System.Random(); //乱数
   string res = "";
   var shio = new string[] { "へー・・・。", "・・・だから?", "知らんわー。", "興味ないね。", "いや、聞いてないし。", "ふーん・・・。で?", "そういうのいいから。", "あーちょっと今忙しいからまた今度。", "・・・けっ!", "リア充乙。" };

   var random = cRandom.Next(11);
   res = shio[random];
   return res;
}

呼び出し側も以下の通り変更します。条件分岐の塩対応部分でgetShioCommentメソッドを呼び出し、結果を出力することと、条件分岐外でres変数を定義し、塩対応、励まし対応それぞれの戻り値をres変数に格納する形にしました。

EchoBot.cs
var client = authenticateClient();
var score = sentimentAnalysisExample(client,turnContext.Activity.Text);//戻り値を格納するためscoreを用意
var res = "";//返事格納用
Debug.WriteLine($"Sentiment Score2: {score:0.00}");//出力確認2

if(0.5 <= score)
{
   res = getShioComment();
   Debug.WriteLine($"塩対応:{res}");//出力確認3
}
else
{
   res = getTweet();
   Debug.WriteLine($"励まし対応:{res}");//出力確認4
}

それではまたここで実行してみましょう。今度はできるだけポジティブな言葉を投げかけてください。
image.png

0.5以上のスコアで塩対応されていれば成功です!

2.6 ボットに発言させる

ここまできたら、後はもう少しです!今までデバッグとして出力させていたコメントを、ボットからユーザーに発言させるようにします。
まず、せっかくですので、返事に分析スコアもつけるため、score変数(小数点以下2桁まで)とres変数を連結します。Environment.NewLineは間で改行をするために入れています。
いよいよユーザーへの発言ですが、OnMessageActivityAsync内のawait turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);部分で、ボットはオウム返しをしていますので、この部分を以下の通り変更して、塩対応/励まし対応の条件分岐後(返事をres変数に格納した後)に持ってきます。

EchoBot.cs
 res = $"Score:{score:0.00}" + Environment.NewLine + res;
 await turnContext.SendActivityAsync(MessageFactory.Text(res), cancellationToken);

また、オウム返しのテキストを作っているvar replyText = $"Echo: {turnContext.Activity.Text}";は不要になるので、削除します。

ここまでの全ソースコードはこちらです。

EchoBot.cs
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.6.2

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;

//for TextAnalytics
using System;
using System.Net.Http;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics.Models;
using Microsoft.Rest;
using System.Diagnostics;

//for Twitter
using CoreTweet;

namespace YasashiiBot.Bots
{
    public class EchoBot : ActivityHandler
    {
        //for TextAnalytics
        private static readonly string key = "<replace-with-your-text-analytics-key-here>;
        private static readonly string endpoint = "<replace-with-your-text-analytics-endpoint-here>";

        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {

            //for TextAnalytics
            var client = authenticateClient();
            var score = sentimentAnalysisExample(client,turnContext.Activity.Text);//戻り値を格納するためscoreを用意
            var res = "";//返事格納用
            Debug.WriteLine($"Sentiment Score2: {score:0.00}");//出力確認2

            if(0.5 <= score)
            {
                res = getShioComment();
                Debug.WriteLine($"塩対応:{res}");//出力確認3
            }
            else
            {
                res = getTweet();
                Debug.WriteLine($"励まし対応:{res}");//出力確認4
            }

            res = $"Score:{score:0.00}" + Environment.NewLine + res;
            await turnContext.SendActivityAsync(MessageFactory.Text(res), cancellationToken);
        }

        protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
        {
            var welcomeText = "Hello and welcome!";
            foreach (var member in membersAdded)
            {
                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
                }
            }
        }

        //for TextAnalytics
        static TextAnalyticsClient authenticateClient()
        {
            ApiKeyServiceClientCredentials credentials = new ApiKeyServiceClientCredentials(key);
            TextAnalyticsClient client = new TextAnalyticsClient(credentials)
            {
                Endpoint = endpoint
            };
            return client;
        }

        //for TextAnalytics
        static double sentimentAnalysisExample(ITextAnalyticsClient client, string message)//戻り値をdouble型に変更
        {
            var result = client.Sentiment(message, "ja");//引数messageを分析対象にし、言語は日本語に設定
            Debug.WriteLine($"User Message: {message}");//引数messageの内容を出力
            Debug.WriteLine($"Sentiment Score1: {result.Score:0.00}");//出力確認1

            return (double)result.Score;//分析スコアを返す
        }
        //for Twitter
        static string getTweet()
        {
            Random cRandom = new System.Random(); //取得したツイートからランダムに1つ選択するための乱数
            var tokens = Tokens.Create("<API KEY>", "<API SECRET>", "<ACCESS TOKEN>", "<ACCESS TOKEN SECRET>");  //接続用トークン発行
            var tweet = "";//取得したツイートを格納する変数

            var parm = new Dictionary<string, object>();  //条件指定用Dictionary
            parm["count"] = 60;  //取得数
            parm["screen_name"] = "hagemasi1_bot";  //取得したいユーザーID


            Task task = Task.Factory.StartNew(async () =>
            {
                var tweets = await tokens.Statuses.UserTimelineAsync(parm); //parmの内容に従ってツイートを取得

                var random = cRandom.Next(61); //0~60の間の乱数を生成
                tweet = tweets[random].Text; //取得した60ツイートからrandom番目のツイートを格納

            }).Unwrap();

            task.Wait();

            return tweet; //選んだツイートを戻り値として返す

        }

        static string getShioComment()
        {
            Random cRandom = new System.Random(); //乱数
            string res = "";
            var shio = new string[] { "へー・・・。", "・・・だから?", "知らんわー。", "興味ないね。", "いや、聞いてないし。", "ふーん・・・。で?", "そういうのいいから。", "あーちょっと今忙しいからまた今度。", "・・・けっ!", "リア充乙。" };

            var random = cRandom.Next(11);
            res = shio[random];
            return res;
        }
    }

    //for TextAnalytics
    class ApiKeyServiceClientCredentials : ServiceClientCredentials
    {
        private readonly string apiKey;

        public ApiKeyServiceClientCredentials(string apiKey)
        {
            this.apiKey = apiKey;
        }

        public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            request.Headers.Add("Ocp-Apim-Subscription-Key", this.apiKey);
            return base.ProcessHttpRequestAsync(request, cancellationToken);
        }
    }
}

2.7 Azureへ再デプロイする

PC上(ローカル)でBot Framework Emulatorを使って十分にテストができたら、Azure上に再度更新版のアプリをデプロイしましょう。
第2部に公式ドキュメントに沿ったデプロイ方法の記載がありますが、ちょっと面倒な手順なので、今回はVisual Studioから直接デプロイしてみます。途中でAzureへのサインインを要求されたら、適宜サインインをお願いします。

まず、ソリューションエクスプローラーから、ソリューション名を右クリックし、発行を選択します。
image.png

次にデプロイ先のAzureリソースを選んでいきます。今回は第2部で作成したAzureリソースに再デプロイするので、App Servive既存のものを選択を選んで、プロファイルの作成をクリックします。
image.png

続いて、App Serviceの詳細を指定します。サブスクリプションの部分で、デプロイ先のAzureサブスクリプションを選びます。表示部分はリソースグループを選択し、下のボックスでデプロイ対象のリソースグループ、WebAppリソースを選択して、OKをクリックします。
image.png

最後に発行をクリックして、デプロイが終わるのを待ちます。
image.png

デプロイされたら、第2部 Webチャットでのテストの手順を参考に、テストをします。
image.png

ポジティブな発言とネガティブな発言をして、適切な返事が返ってきていれば成功です!

以降の工程について

以降の工程はこのようになっています。
次回はいよいよ、Azure上にあるボットアプリと、LINE経由でおしゃべりができるようにしてみたいと思います。

  1. Bot Frameworkでチャットボットを作ってみよう!
  2. ボットをAzure(クラウド)にデプロイしてみよう!
  3. Cognitive Serviceでテキスト分析をしてみよう!
  4. Twitterからつぶやきをとってきてみよう!←イマココ
  5. LINE経由でボットと話してみよう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]PostEffectにUIのスプライトを利用する

PostEffectを掛ける際にUIのスプライト(テクスチャでもいい)を利用したかったメモ。
あまりにもニッチな内容だけど、なかなか苦労したのでメモ残しておきます。

何が問題なのか

ちょっとググれば出るけど、PostEffectを掛けるシェーダーを書く際はまずカメラに処理用のスクリプトを書く。
んで、処理中にRenderTextureを用意して、画面に映ったものをシェーダーで処理してRenderTextureに映す。
シェーダーで描画を実行するにはOnRenderImageで値を渡してGraphics.Blitで描画するんだけど、そのとき_MainTexとして画面のレンダリングイメージが送られる。

_MainTexのUVは画面全体を0-1として送られるので、UIの領域を参照するには画面全体からどうにかして算出しなければならない。

コード

とりあえず上手く行った(っぽい)コード。
説明の必要部分だけ抜粋します。
UIのポジション変換はこちらを参考にさせて頂きました。
http://karanokan.info/2019/02/03/post-2213/

UVは左下から0-1なので、そこに上手く合わせられるようにする。

スクリプト側

public GameObject imageobj = null;
public Texture AddTexture = null;
public Material mat = null;
private RectTransform trfTarget = null;
private RectTransform rectCanvas = null;
private Vector2 ressize = new Vector2(0f,0f);
private Vector2 piv = new Vector2(0f,0f);
private Vector4 rct;

void OnValidate(){
    if(imageobj != null){
        AddTexture = imageobj.GetComponent<Image>().sprite.texture;
        trfTarget = imageobj.GetComponent<RectTransform>();
        var top = imageobj.GetComponent<RectTransform>().root;
        rectCanvas = top.GetComponent<RectTransform>();
        ressize = new Vector2(rectCanvas.rect.width,rectCanvas.rect.height);
        piv = rectCanvas.pivot;
    }
}

private void OnRenderImage(RenderTexture source, RenderTexture dest){
    mat.SetTexture("_AddTex", AddTexture);
    mat.SetVector("_Rect", rct);
    Graphics.Blit(source, dest, mat);
}

private void Update(){
    //World座標をUI座標へ変換
    Vector2 spos = RectTransformUtility.WorldToScreenPoint(Camera.main, trfTarget.position);
    Vector2 pos;
    RectTransformUtility.ScreenPointToLocalPointInRectangle(rectCanvas, spos, Camera.main, out pos);
    //左下を計算上のpivotにする
    var imgsize = new Vector2(trfTarget.rect.width * trfTarget.localScale.x,trfTarget.rect.height * trfTarget.localScale.y);
    var imgpiv = trfTarget.pivot;
    var imgpos = new Vector2(pos.x - (imgsize.x * imgpiv.x),pos.y - (imgsize.y * imgpiv.y));
    //CanvasのRectTransformから解像度とピボットを取り、左下0にする
    var resetpos = new Vector2(imgpos.x + (ressize.x * piv.x),imgpos.y + (ressize.y * piv.y));
    //リニア化 (minx,miny,解像度xに対する横の割合,解像度yに対する高さの割合)
    rct = new Vector4(resetpos.x / ressize.x, resetpos.y / ressize.y, imgsize.x / ressize.x, imgsize.y / ressize.y);
}

シェーダー側

範囲が分かりやすいようにスプライト部分を黒で塗りつぶしてます。
取得したUIのRect情報でいかにしてUVをずらすか。

Shader "UIPostEffect"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AddTex ("AddTexture", 2D) = "white" {}
    }
    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            sampler2D _AddTex;
            float4 _Rect;

            fixed4 frag (v2f_img i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                float2 uv = float2((i.uv.x-_Rect.x)/_Rect.z, (i.uv.y-_Rect.y)/_Rect.w);
                fixed4 addcol = tex2D(_AddTex, uv);
                return lerp(col,fixed4(0,0,0,1),addcol.a);
            }
            ENDCG
        }
    }
    Fallback Off
}

正直上手く説明できない

数学苦手なんでツッコミあったらよろしくお願いします。

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

C#でPythonのNumpyを模倣した

C#でディープラーニング!

PythonのnumpyライブラリをC#で再現しました

再現したと言っても、自分の知る機能の一部だけですが。
ディープラーニングを勉強し始めて、まだまだ間もないですが、
オライリーの「ゼロから作る Deep Learning」を読んで、Pythonでなんとなく作って、
同じ動作をするものをC#で作りました。
C#には、Numpyのようなライブラリーがないので(探せば誰かが作ってるかもしれませんが)、そこから自分で実装していきます。

以前このような記事を投稿しました。

C#で初めてのディープラーニング~Pythonでの実装をまねする~

このときは、簡単な行列計算ができるようにしましたが、今回は、少しだけパワーアップしました。

とりあえず、理解も半端かもしれませんが、そんな中で作ったPythonのディープラーニングプログラムを紹介します。

Pythonで作ったプログラム

deeplearning.py
import numpy as np
import matplotlib.pylab as plt

def sigmoid(x):
    return 1 / (1+np.exp(-x))

def ident(x):
    return x

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)

    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = tmp_val + h
        fxh1 = f(x) # f(x+h)

        x[idx] = tmp_val - h 
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)

        x[idx] = tmp_val # 値を元に戻す
        it.iternext()   

    return grad

def softmax(a):
    c=np.max(a)
    exp_a=np.exp(a-c)
    sum_exp_a=np.sum(exp_a)
    return exp_a/sum_exp_a

def diff(f,x):
    h=1e-4
    return (f(x+h)-f(x-h)/(2*h))

def cross_etp_err(y,t):
    delta=1e-7
    return -np.sum(t*np.log(y+delta))

class testnet:
    def __init__(self):
#-----------------------------------------------------
        self.X=np.array([1.0,0.5])
        self.W1=np.array([[0.0,0.0,0.0],[0.0,0.0,0.0]])
        self.B1=np.array([0.0,0.0,0.0])
#-----------------------------------------------------
        self.W2=np.array([[0.0,0.0],[0.0,0.0],[0.0,0.0]])
        self.B2=np.array([0.0,0.0])
#-----------------------------------------------------
        self.W3=np.array([[0.0,0.0],[0.0,0.0]])
        self.B3=np.array([0.0,0.0])
#-----------------------------------------------------
        self.T=np.array([0,1])

        self.w1_grad=np.zeros_like(self.W1)
        self.b1_grad=np.zeros_like(self.B1)
        self.w2_grad=np.zeros_like(self.W2)
        self.b2_grad=np.zeros_like(self.B2)
        self.w3_grad=np.zeros_like(self.W3)
        self.b3_grad=np.zeros_like(self.B3)
############################
    def loss(self):
        A1=np.dot(self.X,self.W1)+self.B1
        Z1=sigmoid(A1)
        A2=np.dot(Z1,self.W2)+self.B2
        Z2=sigmoid(A2)
        A3=np.dot(Z2,self.W3)+self.B3
        Y=softmax(A3)
        return cross_etp_err(Y,self.T)
############################
net = testnet()

def f(W):
    return net.loss()

rate=0.1

def deep_learning(net):
    net.w1_grad=numerical_gradient(f,net.W1)
    net.b1_grad=numerical_gradient(f,net.B1) 
    net.w2_grad=numerical_gradient(f,net.W2)
    net.b2_grad=numerical_gradient(f,net.B2)
    net.w3_grad=numerical_gradient(f,net.W3)
    net.b3_grad=numerical_gradient(f,net.B3)


    net.W1-=rate*net.w1_grad
    net.B1-=rate*net.b1_grad
    net.W2-=rate*net.w2_grad
    net.B2-=rate*net.b2_grad
    net.W3-=rate*net.w3_grad
    net.B3-=rate*net.b3_grad

loop=0;
while loop<100:
    deep_learning(net)
    print(str(loop)+":"+str(net.B3[0]))
    loop+=1

Numpyをまねて、C#で実装する

deeplearning.cs
using System.Linq;

namespace Matrix
{
    class Mat
    {
        private int r = 0;
        public int R
        {
            get { return r; }
        }
        private int c = 0;
        public int C
        {
            get { return c; }
        }
        private bool err = false;
        public bool Err
        {
            get { return err; }
        }
        private double[][] matrix_data;
        public double[][] Matrix_data
        {
            get {
                double[][] a = new double[2][];
                a[0] = new double[] { 0, 0 };
                a[1] = new double[] { 0, 0 };
                if (err) return a;
                else return matrix_data;
            }
            set
            {
                matrix_data = value;
            }
        }
        public double[][] Zero_matrix
        {
            get
            {
                double[][] zm = new double[this.r][];
                for (int i = 0; i < this.r; i++)
                {
                    zm[i] = new double[this.c];
                    for (int j = 0; j < this.c; j++)
                    {
                        zm[i][j] = 0;
                    }
                }
                return zm;
            }
        }
        public Mat(params double[][] vs)
        {
            int len = vs[0].Length;

            for (int i = 0; i < vs.Length; i++)
            {
                if (i != 0 && len != vs[i].Length)
                {
                    err = true;
                }
            }
            if (!err)
            {
                r = vs.Length;
                c = vs[0].Length;
                matrix_data = vs;
            }
        }
        public double[][] sigmoid()
        {
            double[][] sig = new double[1][];
            sig[0] = new double[this.c];

            for(int i = 0; i < this.c; i++)
            {
                sig[0][i] = 1 / (1 + System.Math.Exp(this.matrix_data[0][i]));
            }

            return sig;
        }
        public double[][] softmax()
        {
            double[][] sm = new double[1][];
            sm[0] = new double[this.c];

            double m = this.matrix_data[0].Max();

            double[] exp_a = new double[this.c];
            for (int i = 0; i < this.c; i++)
            {
                exp_a[i] = System.Math.Exp(this.matrix_data[0][i] - m);
            }

            double sum = 0.0;
            for (int i = 0; i < this.c; i++)
            {
                sum = sum + exp_a[i];
            }

            for (int i = 0; i < this.c; i++)
            {
                sm[0][i] = exp_a[i] / sum;
            }

            return sm;
        }
        public double cross_etp_err(Mat t)
        {
            double delta = 0.0000001;
            double sum = 0.0;
            for (int i = 0; i < this.c; i++)
            {
                sum = sum + t.matrix_data[0][i] * System.Math.Log(this.matrix_data[0][i] + delta);
            }

            return -sum;
        }
        public double[][] numerical_gradient(System.Func<double> loss)
        {
            double h = 0.0001;
            double[][] grad = new double[this.r][];
            double tmp_val = 0.0;
            double fxh1 = 0.0;
            double fxh2 = 0.0;

            for(int i = 0; i < this.r; i++)
            {
                grad[i] = new double[this.c];
                for(int j = 0; j < this.c; j++)
                {
                    tmp_val = this.matrix_data[i][j];
                    this.matrix_data[i][j] = tmp_val + h;
                    fxh1 = loss();

                    this.matrix_data[i][j] = tmp_val - h;
                    fxh2 = loss();

                    grad[i][j] = (fxh1 - fxh2) / (2 * h);
                    this.matrix_data[i][j] = tmp_val;
                }
            }

            return grad;
        }
        //以下 演算子オーバーロード
        public static double[][] operator +(Mat p1, Mat p2)
        {
            double[][] d = new double[p1.R][];

            if (p1.C == p2.C && p1.R == p2.R)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p1.C];
                    for (int j = 0; j < p1.C; j++)
                    {
                        d[i][j] = p1.Matrix_data[i][j] + p2.Matrix_data[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator +(double[][] p1, Mat p2)
        {
            double[][] d = new double[p1.Length][];

            if (p1[0].Length == p2.C && p1.Length == p2.R)
            {
                for (int i = 0; i < p1.Length; i++)
                {
                    d[i] = new double[p1[0].Length];
                    for (int j = 0; j < p1[0].Length; j++)
                    {
                        d[i][j] = p1[i][j] + p2.Matrix_data[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.Length; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator +(Mat p1, double[][] p2)
        {
            double[][] d = new double[p1.R][];

            if (p1.C == p2[0].Length && p1.R == p2.Length)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p1.C];
                    for (int j = 0; j < p1.C; j++)
                    {
                        d[i][j] = p1.Matrix_data[i][j] + p2[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator +(double p1, Mat p2)
        {
            double[][] d = new double[p2.R][];
            for (int i = 0; i < p2.R; i++)
            {
                d[i] = new double[p2.C];
                for (int j = 0; j < p2.C; j++)
                {
                    d[i][j] = p2.Matrix_data[i][j] + p1;
                }
            }

            return d;
        }
        public static double[][] operator +(Mat p1, double p2)
        {
            double[][] d = new double[p1.R][];
            for (int i = 0; i < p1.R; i++)
            {
                d[i] = new double[p1.C];
                for (int j = 0; j < p1.C; j++)
                {
                    d[i][j] = p1.Matrix_data[i][j] + p2;
                }
            }

            return d;
        }
        public static double[][] operator -(Mat p1, Mat p2)
        {
            double[][] d = new double[p1.R][];

            if (p1.C == p2.C && p1.R == p2.R)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p1.C];
                    for (int j = 0; j < p1.C; j++)
                    {
                        d[i][j] = p1.Matrix_data[i][j] - p2.Matrix_data[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator -(double[][] p1, Mat p2)
        {
            double[][] d = new double[p1.Length][];

            if (p1[0].Length == p2.C && p1.Length == p2.R)
            {
                for (int i = 0; i < p1.Length; i++)
                {
                    d[i] = new double[p1[0].Length];
                    for (int j = 0; j < p1[0].Length; j++)
                    {
                        d[i][j] = p1[i][j] - p2.Matrix_data[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.Length; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator -(Mat p1, double[][] p2)
        {
            double[][] d = new double[p1.R][];

            if (p1.C == p2[0].Length && p1.R == p2.Length)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p1.C];
                    for (int j = 0; j < p1.C; j++)
                    {
                        d[i][j] = p1.Matrix_data[i][j] - p2[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }

        public static double[][] operator -(double p1, Mat p2)
        {
            double[][] d = new double[p2.R][];
            for (int i = 0; i < p2.R; i++)
            {
                d[i] = new double[p2.C];
                for (int j = 0; j < p2.C; j++)
                {
                    d[i][j] = p1 - p2.Matrix_data[i][j];
                }
            }

            return d;
        }
        public static double[][] operator -(Mat p1, double p2)
        {
            double[][] d = new double[p1.R][];
            for (int i = 0; i < p1.R; i++)
            {
                d[i] = new double[p1.C];
                for (int j = 0; j < p1.C; j++)
                {
                    d[i][j] = p1.Matrix_data[i][j] - p2;
                }
            }

            return d;
        }
        public static double[][] operator *(Mat p1, Mat p2)
        {
            double[][] d = new double[p1.R][];

            if (p1.C == p2.C && p1.R == p2.R)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p1.C];
                    for (int j = 0; j < p1.C; j++)
                    {
                        d[i][j] = p1.Matrix_data[i][j] * p2.Matrix_data[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator *(double[][] p1, Mat p2)
        {
            double[][] d = new double[p1.Length][];

            if (p1[0].Length == p2.C && p1.Length == p2.R)
            {
                for (int i = 0; i < p1.Length; i++)
                {
                    d[i] = new double[p1[0].Length];
                    for (int j = 0; j < p1[0].Length; j++)
                    {
                        d[i][j] = p1[i][j] * p2.Matrix_data[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.Length; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator *(Mat p1, double[][] p2)
        {
            double[][] d = new double[p1.R][];

            if (p1.C == p2[0].Length && p1.R == p2.Length)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p1.C];
                    for (int j = 0; j < p1.C; j++)
                    {
                        d[i][j] = p1.Matrix_data[i][j] * p2[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }

        public static double[][] operator *(double p1, Mat p2)
        {
            double[][] d = new double[p2.R][];
            for (int i = 0; i < p2.R; i++)
            {
                d[i] = new double[p2.C];
                for (int j = 0; j < p2.C; j++)
                {
                    d[i][j] = p1 * p2.Matrix_data[i][j];
                }
            }

            return d;
        }
        public static double[][] operator *(Mat p1, double p2)
        {
            double[][] d = new double[p1.R][];
            for (int i = 0; i < p1.R; i++)
            {
                d[i] = new double[p1.C];
                for (int j = 0; j < p1.C; j++)
                {
                    d[i][j] = p1.Matrix_data[i][j] * p2;
                }
            }

            return d;
        }
        public static double[][] operator /(Mat p1, Mat p2)
        {
            double[][] d = new double[p1.R][];

            if (p1.C == p2.C && p1.R == p2.R)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p1.C];
                    for (int j = 0; j < p1.C; j++)
                    {
                        d[i][j] = p1.Matrix_data[i][j] / p2.Matrix_data[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator /(double[][] p1, Mat p2)
        {
            double[][] d = new double[p1.Length][];

            if (p1[0].Length == p2.C && p1.Length == p2.R)
            {
                for (int i = 0; i < p1.Length; i++)
                {
                    d[i] = new double[p1[0].Length];
                    for (int j = 0; j < p1[0].Length; j++)
                    {
                        d[i][j] = p1[i][j] / p2.Matrix_data[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.Length; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
        public static double[][] operator /(Mat p1, double[][] p2)
        {
            double[][] d = new double[p1.R][];

            if (p1.C == p2[0].Length && p1.R == p2.Length)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p1.C];
                    for (int j = 0; j < p1.C; j++)
                    {
                        d[i][j] = p1.Matrix_data[i][j] / p2[i][j];
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }

        public static double[][] operator /(double p1, Mat p2)
        {
            double[][] d = new double[p2.R][];
            for (int i = 0; i < p2.R; i++)
            {
                d[i] = new double[p2.C];
                for (int j = 0; j < p2.C; j++)
                {
                    d[i][j] = p1 / p2.Matrix_data[i][j];
                }
            }

            return d;
        }
        public static double[][] operator /(Mat p1, double p2)
        {
            double[][] d = new double[p1.R][];
            for (int i = 0; i < p1.R; i++)
            {
                d[i] = new double[p1.C];
                for (int j = 0; j < p1.C; j++)
                {
                    d[i][j] = p1.Matrix_data[i][j] / p2;
                }
            }

            return d;
        }
        public static double[][] dot(Mat p1, Mat p2)
        {
            double[][] d = new double[p1.R][];
            double temp = 0;

            if (p1.C == p2.R)
            {
                for (int i = 0; i < p1.R; i++)
                {
                    d[i] = new double[p2.C];
                    for (int j = 0; j < p2.C; j++)
                    {
                        for(int a = 0; a < p1.C; a++)
                        {
                            temp = temp + p1.Matrix_data[i][a] * p2.Matrix_data[a][j];
                        }
                        d[i][j] = temp;
                        temp = 0.0;
                    }
                }
            }
            else
            {
                for (int k = 0; k < p1.R; k++)
                {
                    d[k] = new double[2] { 0, 0 };
                }
            }

            return d;
        }
    }
}

一つのクラスにまとめましたので、ライブラリー化して使用することもできます。

C#のディープラーニングプログラム

main.cs
namespace Matrix
{
    class Program
    {
        static Mat X = new Mat(
                new double[] { 1.0, 0.5 }
                ),
            W1 = new Mat(
                new double[] { 0.0, 0.0, 0.0 },
                new double[] { 0.0, 0.0, 0.0 }
                ),
            B1 = new Mat(
                new double[] { 0.0, 0.0, 0.0 }
                ),
            W2 = new Mat(
                new double[] { 0.0, 0.0 },
                new double[] { 0.0, 0.0 },
                new double[] { 0.0, 0.0 }
                ),
            B2 = new Mat(
                new double[] { 0.0, 0.0 }
                ),
            W3 = new Mat(
                new double[] { 0.0, 0.0 },
                new double[] { 0.0, 0.0 }
                ),
            B3 = new Mat(
                new double[] { 0.0, 0.0 }
                ),
            T = new Mat(
                new double[] { 0, 1 }
                );

        static double loss()
        {
            Mat A1 = new Mat(
                new double[] { 0.0, 0.0, 0.0 }
                ),
                Z1 = new Mat(
                new double[] { 0.0, 0.0, 0.0 }
                ),
                A2 = new Mat(
                new double[] { 0.0, 0.0 }
                ),
                Z2 = new Mat(
                new double[] { 0.0, 0.0 }
                ),
                A3 = new Mat(
                new double[] { 0.0, 0.0 }
                ),
                Y = new Mat(
                new double[] { 0.0, 0.0 }
                );

            double[][] eeeeee = Mat.dot(X, W1);

            A1.Matrix_data = Mat.dot(X, W1) + B1;
            Z1.Matrix_data = A1.sigmoid();
            A2.Matrix_data = Mat.dot(Z1, W2) + B2;
            Z2.Matrix_data = A2.sigmoid();
            A3.Matrix_data = Mat.dot(Z2, W3) + B3;
            Y.Matrix_data = A3.softmax();

            return Y.cross_etp_err(T);
        }
        static void Main(string[] args)
        {
            double rate = 0.1;

            Mat W1_grad = new Mat(W1.Zero_matrix),
                B1_grad = new Mat(B1.Zero_matrix),
                W2_grad = new Mat(W2.Zero_matrix),
                B2_grad = new Mat(B2.Zero_matrix),
                W3_grad = new Mat(W3.Zero_matrix),
                B3_grad = new Mat(B3.Zero_matrix);
            for (int i = 0; i < 100; i++)
            {
                W1_grad.Matrix_data = W1.numerical_gradient(loss);
                B1_grad.Matrix_data = B1.numerical_gradient(loss);
                W2_grad.Matrix_data = W2.numerical_gradient(loss);
                B2_grad.Matrix_data = B2.numerical_gradient(loss);
                W3_grad.Matrix_data = W3.numerical_gradient(loss);
                B3_grad.Matrix_data = B3.numerical_gradient(loss);

                W1.Matrix_data = W1 - (rate * W1_grad);
                B1.Matrix_data = B1 - (rate * B1_grad);
                W2.Matrix_data = W2 - (rate * W2_grad);
                B2.Matrix_data = B2 - (rate * B2_grad);
                W3.Matrix_data = W3 - (rate * W3_grad);
                B3.Matrix_data = B3 - (rate * B3_grad);

                System.Console.WriteLine(i.ToString() + ":" + B3.Matrix_data[0][0].ToString());
            }
        }
    }
}

Matクラスのコンストラクターの引数は、params double[]double[][]型を取ります。
Mat同士や、実数、double[][]との加減乗除が行えます。
また、ソフトマックス関数や、シグモイド関数、勾配を求める、公差エントロピー誤差などの計算が可能です。

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

UniRxでボタンから離したときに発火したいが、押している間にボタンの領域から離れたら無効にしたい時

要件

ボタン押したタイミングではまだ発火せず、離したタイミングでOnNextを送ってほしい。

ただし、押しながらボタンの領域からスワイプのようにずらしていって、最終的にボタンを離した場所がボタンの上でなければ、OnNextは送らないでほしい。

解決

var btnPressStream = btn.OnPointerClickAsObservable();

P.S.

もし最終的に離れた場所がボタン上でなくてもよければ、 OnPointerUpAsObservable() を使う。

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

やさしいチャットボットのつくりかた -3.Cognitive Serviceでテキスト分析をしてみよう!

どういう記事?

Azure素人の自分がMicrosoft AzureのBot FrameworkとCognitive Serviceと触れ合いながら、難易度も人への接し方も“やさしい”チャットボットを作ってみた際の備忘録の第3部です。どんなチャットボットを作るのかを含め、これまでの手順は第1部第2部にございます。
PowerPointで記録していたものをQiita化してみたものになるので、そんな感じの画像が頻出します。
Qiita初心者なので、書き方が下手でも広い心で許してほしいです。そして全体的にゆるいです。

アジェンダ

以下の5部構成になります。この記事は第3部にあたります。

  1. Bot Frameworkでチャットボットを作ってみよう!
  2. ボットをAzure(クラウド)にデプロイしてみよう!
  3. Cognitive Serviceでテキスト分析をしてみよう!←イマココ
  4. Twitterからつぶやきをとってきてみよう!
  5. LINE経由でボットと話してみよう!

なお、公式ドキュメントに沿って作りましたので、それらのリンクを張り付けつつ、辿った道筋通りに書いていきます。
その筋の方からすると遠回りだったり野暮ったかったりすると思いますが、ご容赦ください。

2. Cognitive Serviceでテキスト分析をしてみよう!

これまで、第1部で作ったソースコードを、第2部でAzure(クラウド)上へデプロイしましたので、今回はユーザーから受け取ったコメントをAzure Cognitive ServiceText Analyticsを使って、ネガティブorポジティブの判定をしてみたいと思います(図中の赤枠部分)。
image.png

2.1 参考ドキュメント

クイック スタート:Text Analytics クライアント ライブラリを使用する

今回の作業は公式ドキュメントのクイック スタート:Text Analytics クライアント ライブラリを使用するを参考にしています。
なお、最新のプレビュー バージョンは 3.0-previewとのことですが、今回は安定版である2.1を使ってみました。

2.2 前提条件

まずは上記リンク内の「前提条件」の部分に従って、以下を準備します。

  • Microsoft Azure のサブスクリプション
  • Visual Studio IDE
  • Text Analytics リソースの作成(キーとエンドポイントを取得)

Microsoft Azure のサブスクリプションの準備

第2部で準備済みかと思いますので、それを使います。

Visual Studio IDE

第1部で使ったVisual Studio 2019を使います。

Text Analytics リソースの作成(キーとエンドポイントを取得)

Azure portal を使用して Cognitive Services リソースを作成するのクイックスタートに沿って、Text Analyticsリソースを作成します。こちらは割とつまずくところもなく、公式ドキュメントそのままで作成可能なので、そちらを参照いただいても大丈夫かと思います。

新しい Azure Cognitive Services リソースを作成する

リンク先の新しい Azure Cognitive Services リソースを作成するのところで、単一サービスリソースのタブから、Text Analyticsを選択します。

image.png

すると、AzurePortalへ自動で飛ばされ、TextAnalyticsリソース作成の画面に移りますので、それぞれ値を設定していきます。
image.png

  • 名前
    TextAnalyticsリソースの名前です。なんでもよいです。今回は例として、YasashiiBotTextAnalyticsとしています。
  • サブスクリプション
    TextAnalyticsリソースをどのAzureサブスクリプションで作成するかということです。適切なものを選択してください。
  • 場所
    TextAnalyticsリソースをどのリージョンに配置するかということです。今回は東日本を選択します。
  • 価格レベル
    価格レベルを選択します。今回はテストなので、一番下のレベルF0を選択します。
  • リソースグループ
    TextAnalyticsリソースをどのリソースグループに作成するかということです。今回は第2部で作成したリソースグループを選択しました。

全ての項目で設定ができたら、左下の作成ボタンをクリックします。

リソースのキーを取得する

少し待つと、デプロイが完了しました、という画面になりますので、リソースに移動ボタンをクリックします。
image.png

APIキーとエンドポイントが表示されます。後程使いますので、コピーしておいてください。
image.png

2.3 Text Analyticsの設定

ここから先、コードを書いていきますが、筆者はコーディング力は皆無(とりあえず動くものを作るだけ)なので、清く正しく美しいコードを書けるようになりたい方は、参考にしすぎないようにしてください。ごめんなさい。

公式ドキュメントだと、新しいアプリを作成するところからですが、第1部で作成済みなので、そちらを使っていきます。プロジェクトを開いて、ソリューションエクスプローラーからソリューション名を右クリックし、NuGetパッケージの管理を選択します。
image.png

NuGetパッケージマネージャーが開いたら、Microsoft.Azure.CognitiveServices.Language.TextAnalyticsと検索し、パッケージをインストールします。
image.png

公式ドキュメントではprogram.csへの追加の手順が記載されていますが、今回はEchoBot.csに全てまとめてしまおうと思うので、EchoBot.csの上の方(usingほげほげが書いてあるところ)に以下の通り追加します。これは、公式ドキュメントに書いてあるものと、EchoBot.csに元々書いてあったものの差分です。

EchoBot.cs
using System;
using System.Net.Http;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics.Models;
using Microsoft.Rest;

追加するとこんな感じになります。わかりやすいように、今回TextAnalytics連携のために追加したコードの上には、//for TextAalyticsと書いてあります。
image.png

次に、public class EchoBotに、先ほど作成したTextAnalyticsのAPIキーとエンドポイント(リソースのキーを取得するで表示されたもの)を以下の通り追加します。

EchoBot.cs
private static readonly string key = "<replace-with-your-text-analytics-key-here>";
private static readonly string endpoint = "<replace-with-your-text-analytics-endpoint-here>";

追加すると、こんな感じになります。
image.png

さて、いよいよ、TextAnalyticsとの連携部分を作っていきます。公式ドキュメントでは新しく.NET Core コンソールアプリを作った例になっているため、Mainメソッド(アプリを実行したらまず開始される部分)を書き換えるように書いてありますが、今回の場合はユーザーが話しかけてきたら発動するようにしたいので、以下の2行をOnMessageActivityAsyncのタスク内に追加します。

EchoBot.cs
var client = authenticateClient();
sentimentAnalysisExample(client);

各メソッドの処理内容としては以下の通りです。

  • authenticateClient()
    新しい Azure Cognitive Services リソースを作成するで作成したTextAnalyticsリソースを、このボットアプリから利用するための認証をする。

  • sentimentAnalysisExample(client)
    authenticateClient()で認証されたTextAnalyticsリソースを使って、任意のメッセージの感情分析をして、スコアを得る。

追加するとこんな感じになります。この状態だと記載したメソッドの本体が存在しないため、赤い波線が出てエラーになってしまっていますが、一旦無視してください。
image.png

2.4 クライアントを認証する

このボットアプリから、先ほど作ったTextAnalyticsリソースを使えるように認証をするため、authenticateClient()の中身を作っていきます。以下のクラスを追加します。クラスなので、public class EchoBotと同じレベル(クラス名の左側の深さを合わせる)で記載します。

EchoBot.cs
class ApiKeyServiceClientCredentials : ServiceClientCredentials
{
    private readonly string apiKey;

    public ApiKeyServiceClientCredentials(string apiKey)
    {
        this.apiKey = apiKey;
    }

    public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request == null)
        {
            throw new ArgumentNullException("request");
        }
        request.Headers.Add("Ocp-Apim-Subscription-Key", this.apiKey);
        return base.ProcessHttpRequestAsync(request, cancellationToken);
    }
}

追加するとこんな感じになります。
image.png

次に、先ほどエラーになっていたauthenticateClientを呼び出せるようにしていきます。public class EchoBotに戻って、以下を追加します。既にpublic class EchoBot内にある、他のタスクと同じレベル(深さ)に記載します。

EchoBot.cs
static TextAnalyticsClient authenticateClient()
{
    ApiKeyServiceClientCredentials credentials = new ApiKeyServiceClientCredentials(key);
    TextAnalyticsClient client = new TextAnalyticsClient(credentials)
    {
        Endpoint = endpoint
    };
    return client;
}

追加するとこんな感じになります。
image.png

2.5 センチメント分析

次に、もう一つエラーになっていた、SentimentAnalysisExample() というメソッドを作成します。ここで、感情分析をするテキストの指定と、その結果の受け取りをします。以下のコードをpublic class EchoBotに追加します。こちらも、既に存在している他のタスクと同じレベル(深さ)に記載します。

EchoBot.cs
static void sentimentAnalysisExample(ITextAnalyticsClient client)
{
    var result = client.Sentiment("I had the best day of my life.", "en");
    Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
}

ここでは、サンプルとして、「I had the best day of my life.」という英語の文章の感情分析をしてみています。

追加するとこんな感じになります。
image.png

ここで、Debug.WhiteLineの部分にエラーが出ていることに気づくかと思います。これは、拡張機能が不足している(上の方でusingしているものが、Debug.WhiteLineを使うのには足りていない)ために起きています。Debug部分にカーソルを合わせて、考えられる修正内容を表示するを選択し、using System.Diagnosticsの追加の提案を受け入れてください。すると自動的にソースコード上部にusing System.Diagnosticsが追加され、エラーも消えます。

image.png

さて、この状態で、一応ボットアプリ→TextAnalyticsリソースの利用はできるようになっていますので、一旦▶IIS Expressからローカルで実行し、Bot Framework Emulatorでボットに話しかけてみてください。

image.png
こんな感じで、出力画面にSentiment Scoreが出てきたら成功です。現時点では、ボットに話しかけたメッセージの分析ではなく、ソースコードに直接書いた「I had the best day of my life.」の分析結果ではありますが、きちんと値が返ってくれば、ボットアプリからTextAnalyticsリソースを利用し、分析結果を得ることができていることの確認ができます。

2.6 ユーザーのメッセージをTextAnalyticsで分析する

次に、ユーザーのメッセージをTextAnalyticsで分析するように、ソースコードを変更します。ユーザーのメッセージが入っているのはOnMessageActivityAsync内のturnContext.Activity.Textの部分ですので、これをSentimentAnalysisExampleメソッドに渡すように、コードを変更します。具体的には、sentimentAnalysisExampleの引数に、turnContext.Activity.Textを追加します。

EchoBot.cs
sentimentAnalysisExample(client,turnContext.Activity.Text);//引数turnContext.Activity.Textを追加

このような感じになります。
image.png

次に、受け取るsentimentAnalysisExample側も変更が必要ですので、以下の通り変更します。

EchoBot.cs
static void sentimentAnalysisExample(ITextAnalyticsClient client, string message)//引数messageを追加
{
   var result = client.Sentiment(message, "ja");//引数messageを分析対象にし、言語は日本語に設定
   Debug.WriteLine($"User Message: {message}");//引数messageの内容を出力
   Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
}

このような感じになります。
image.png

ここまで変更したら、またアプリを実行して、ボットに話しかけてみましょう。今度は日本語で、意味のある言葉を投げかけてみてください。

image.png

このように、話しかけた内容と、分析結果のスコアが返ってきていれば成功です!

2.7 現時点でのEchoBot.cs

現時点でのEchoBot.csのソースコードはこのような状態になります。

EchoBot.cs
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// Generated with Bot Builder V4 SDK Template for Visual Studio EchoBot v4.6.2

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;

//for TextAnalytics
using System;
using System.Net.Http;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics;
using Microsoft.Azure.CognitiveServices.Language.TextAnalytics.Models;
using Microsoft.Rest;
using System.Diagnostics;

namespace YasashiiBot.Bots
{
    public class EchoBot : ActivityHandler
    {
        //for TextAnalytics
        private static readonly string key = "<replace-with-your-text-analytics-key-here>";
        private static readonly string endpoint = "<replace-with-your-text-analytics-endpoint-here>";

        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
        {
            var replyText = $"Echo: {turnContext.Activity.Text}";
            await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);

            //for TextAnalytics
            var client = authenticateClient();
            sentimentAnalysisExample(client,turnContext.Activity.Text);//引数turnContext.Activity.Textを追加
        }

        protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
        {
            var welcomeText = "Hello and welcome!";
            foreach (var member in membersAdded)
            {
                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text(welcomeText, welcomeText), cancellationToken);
                }
            }
        }

        //for TextAnalytics
        static TextAnalyticsClient authenticateClient()
        {
            ApiKeyServiceClientCredentials credentials = new ApiKeyServiceClientCredentials(key);
            TextAnalyticsClient client = new TextAnalyticsClient(credentials)
            {
                Endpoint = endpoint
            };
            return client;
        }

        //for TextAnalytics
        static void sentimentAnalysisExample(ITextAnalyticsClient client, string message)//引数messageを追加
        {
            var result = client.Sentiment(message, "ja");//引数messageを分析対象にし、言語は日本語に設定
            Debug.WriteLine($"User Message: {message}");//引数messageの内容を出力
            Debug.WriteLine($"Sentiment Score: {result.Score:0.00}");
        }
    }

    //for TextAnalytics
    class ApiKeyServiceClientCredentials : ServiceClientCredentials
    {
        private readonly string apiKey;

        public ApiKeyServiceClientCredentials(string apiKey)
        {
            this.apiKey = apiKey;
        }

        public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            request.Headers.Add("Ocp-Apim-Subscription-Key", this.apiKey);
            return base.ProcessHttpRequestAsync(request, cancellationToken);
        }
    }
}

以降の工程について

以降の工程はこのようになっています。
次回はTwitterと連携して、感情分析の結果がネガティブ判定だった場合、偉人さんのありがたいお言葉を返す部分を作ってみたいと思います。

  1. Bot Frameworkでチャットボットを作ってみよう!
  2. ボットをAzure(クラウド)にデプロイしてみよう!
  3. Cognitive Serviceでテキスト分析をしてみよう!←イマココ
  4. Twitterからつぶやきをとってきてみよう!
  5. LINE経由でボットと話してみよう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

面倒な等値アサートを簡潔に書く

本記事は C# でテストを記述する人を対象に、しばしば複合型の等値アサートで生じる「面倒さ」に対するソリューションの一つを紹介します。テストフレームワークはなんでも。

その前に、先ずはシンプルな (そして理想的な) 等値アサートから:

public string GetFoo() {
    return "Foo";
}

...

string actual = GetFoo();
string expected = "Foo";

Assert.AreEqual(expected, actual);  // 成功!

アサート対象が intstring といった基本型なら、たいてい困る事もなく素直に書けます。
しかし、対象がユーザー定義型のような複合型だとそうは問屋が卸さない。

面倒な等値アサート

例えば Account というユーザー定義クラスと、それを返すクエリ サービス AccountQuery があったとします:

class Account {
  public int Id { get; set; }
  public string Name { get; set; }
  public List<string> Tags { get; } = new List<string>();
}

class AccountQuery {
  /// 例なので固定データを返すだけ。
  public Account Find(int id) {
    return new Account { Id = id, Name = "Foo" + id, Tags = { "tag1" } };
  }
}

次に、AccountQuery が返す Account が期待通りか調べるテストを書いてみます:

Account actual = new AccountQuery().Find(id: 1);
Account expected = new Account { Id = 1, Name = "Foo1", Tags = { "tag1" } };

//Assert.AreEqual(expected, actual);  // 大抵のテストフレームワークで参照比較となり失敗する。ので、
Assert.AreEqual(expected.Id, actual.Id);  // データメンバー毎に等値アサートが必要
Assert.AreEqual(expected.Name, actual.Name);
CollectionAssert.AreEqual(expected.Tags, actual.Tags);  // コレクションには専用の Assert が必要な事が殆ど
// Account に Enabled プロパティが足されたらアサート忘れそう・・・

等値アサートしたい actual は一つだけなのに、わざわざデータメンバーの数だけ Assert.AreEqual() を書くのは大変です。しかもデータメンバーの型に応じて専用の Assert メソッドを使い分ける必要もあったり。 Assert.AreEqual() で全部いい感じにやってくれYO

更に問題なのが、Account に新しいプロパティが追加された場合でも、上記のテストは追加プロパティをアサートする事なく成功してしまうという事。これはマズイ。

テストフレームワークによっては IEuqalityComparer<T> 等のカスタム等値性を Assert に与える事でフォローできる場合もありますが、あまりにも自明な等値アサートをしたいだけなのに新たに型を実装するとか面倒すぎます。

このように、等値アサートの書き方に時間を取られるのはもうウンザリ :confounded:

PrimitiveAssert で簡潔に書く

https://www.nuget.org/packages/Inasync.PrimitiveAssert/

PrimitiveAssert はテスト対象データをいくつかの基本型(プリミティブ データ)に分解し、個別に比較します。
API は基本的に次の拡張メソッド一つだけです。

actual.AssertIs(expected);

これで書き換えてみましょう:

Account actual = new AccountQuery().Find(id: 1);
Account expected = new Account { Id = 1, Name = "Foo1", Tags = { "tag1" } };

actual.AssertIs(expected);  // 成功!

今度はいい感じに等値アサートしてくれます! 見た目もシンプルになりました!!
データメンバーにコレクションがあっても要素に分解して等値アサートします。

API も一つだけなので、基本的にはこれだけです。
そう、これぐらい簡単でいいんだよ :sob:

もう少し詳解

コレクションの等値アサートはできる?

Yes!
汎用的なコレクション (System.Collections 以下にあるような型) 同士であれば、型に関わらずコレクションとして等値アサートされます。

List<string> actual = new List<string> { "foo", "bar" };
string[] expected = new[] { "foo", "bar" };

actual.AssertIs(expected);  // 成功!

タプルの等値アサートはできる?

Yes!
Tuple 同士、ValueTuple 同士はもちろん、TupleValueTuple の等値アサートでも問題ありません。

var actual = Tuple.Create("foo", "bar");
var expected = ("foo", "bar");

actual.AssertIs(expected);  // 成功!

循環参照の等値アサートはできる?

Yes!
ウロボロスに囚われる事はありません。

class Foo {
    public Foo Self => this;
}

...

Foo actual = new Foo();
Foo expected = new Foo();

actual.AssertIs(expected);  // 成功!

本当に全てのデータメンバーが等値アサートされている?

という場合は、コンソールにログを出力してみることもできます。

// AssertIs() を呼び出す前、テストのセットアップ処理とかに書いておく。
// 既定値は false
PrimitiveAssert.ConsoleLogging = true;

だいたい下記のような出力が得られます。

Console
actual と expected は数値型として等しいです。
{
      path: ./Id:Int32
    target: System.Int32
    actual: 1
  expected: 1
}
actual と expected は String 型として等しいです。
{
      path: ./Name:String
    target: System.String
    actual: Foo1
  expected: Foo1
}
actual と expected は String 型として等しいです。
{
      path: ./Tags:List`1/0:String
    target: System.String
    actual: tag1
  expected: tag1
}

expected は匿名型でも良い?

Yes!
PrimitiveAssert は基本的に型を比較しません。代わりにターゲット型として指定した型のデータメンバーを全て満たしているか否かを検証します。その為、expected は匿名型でも、全く関係のない HogeHoge 型でも問題ありません。

また、ターゲット型の指定は任意で、省略時には actual の型(ここでは Account)が設定されます。

Account actual = new AccountQuery().Find(id: 1);
var expected = new { Id = 1, Name = "Foo1", Tags = new[] { "tag1" } };

actual.AssertIs(expected);  // 成功!
actual.AssertIs<Account>(expected);  // 型パラメーターでターゲット型を指定できる

expected にはターゲット型の全てのデータメンバーが必要

前述の通り、expected は型に縛られない代わりに、ターゲット型の全てのデータメンバーを満たす必要があります。
これにより、後々 AccountEnabled プロパティが追加された場合、expected にも Enabled が無いと失敗するようにもなりました。これでデータメンバーがアサートから漏れている状況を検知する事ができます。

class Account {
  ...
  public bool Enabled { get; set; }  // 新たに追加したプロパティ
}

class AccountQuery {
  public Account Find(int id) {
    return new Account { Id = id, Name = "Foo" + id, Tags = { "tag1" }, Enabled = true };
  }
}

...

var actual = new AccountQuery().Find(id: 1);
//actual.AssertIs(new { Id = 1, Name = "Foo1", Tags = new[] { "tag1" } });  // Enabled が無いので失敗
actual.AssertIs(new { Id = 1, Name = "Foo1", Tags = new[] { "tag1" }, Enabled = true });  // 成功!

More info

README.md には PrimitiveAssert の詳細とより多くの事例が載っています。ぜひ参照してみてください。

つまり?

PrimitiveAssert を使えばたいていの等値アサートは簡潔に書けます!

var actual = new { Foo = "Foo1", Bar = new List<int>{ 1, 2 } };

actual.AssertIs(new { Foo = "Foo1", Bar = new[] { 1, 2 } });  // 成功!

参照

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

突然CORSで怒られたときの対処法(.NET Core)

いつも通り、APIを作り、Chromeからリクエストしたところ、今まで普通にリクエストができていたのに、突然CORSで怒られるようになったので、調べてみた。

発生事象

ChromeではCORS、Edgeでは404が返ってきた。

Chrome

Access to XMLHttpRequest at 'https://localhost:44342/api/{長いため以下略}' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Edge
image.png

その他の検索メソッドなどは普通に動いている&クエリを減らせばリクエストが通るため、クエリに何らかの問題があるのではないかとにらんだ。

原因

結論から言いますと、Getのクエリ文字列が長すぎる場合に、.NETでは404.15というものを返しているということが分かった。

404の先入観から、これは想定外だった。

image.png

公式ドキュメントにもそう記載があった。

404.15 - クエリ文字列が長すぎます。

どうやらクエリは2048バイトまでらしい。
https://docs.microsoft.com/en-us/aspnet/whitepapers/aspnet4/overview#0.2__Toc253429244
https://docs.microsoft.com/ja-jp/dotnet/api/system.web.configuration.httpruntimesection.maxquerystringlength?view=netframework-4.8

対処法

ググってみると、web.configに設定を記載すればよいとのこと。

IIS7以降でURLクエリ文字列が長いと404エラー - ゆれくるコール開発日誌

しかし、.NET Coreの場合はweb.configがないのですが、作ってあげれば読み込んでくれるようです。

ASP.NET Core アプリケーションでも web.config をカスタマイズしたい - しばやん雑記

プロジェクト直下にweb.configファイルを作成し、以下の設定をします。

maxQueryStringパラメータを増やしてあげればOKです。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <security>
        <requestFiltering>
          <requestLimits maxQueryString="10000" />
        </requestFiltering>
      </security>
    </system.webServer>
  </location>
</configuration>
<!--ProjectGuid: {略}-->

これで再実行すると、長いクエリ文字列でも受け付けてくれ、ChromeでCORSが出なくなりました。

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

C#でクラスライブラリを開発する時に必ずやった方が便利な設定

はじめに

C#でクラスライブラリを開発する時に、必ずやった方が便利な設定の紹介です。
前提として、その開発したクラスライブラリのDLLファイルが、別のソリューション(.sln)から参照される場合を想定しています。
(同じソリューション内で[プロジェクト]の参照しかしない場合は、本稿の設定をしなくても問題ありません)

デフォルト設定のままで不便なこと

開発したクラスライブラリを、デフォルト設定のままで別のソリューションから参照させた場合、以下の2つの不便なことが起きます。
ここで言う「デフォルト設定」とは、Release ビルドしたDLLファイルを参照させた場合を意味します。

インテリセンスでコメントが表示されない

開発したクラスライブラリが、以下のようにプロパティやメソッドのコメントを記載しているとします。

    /// <summary>
    /// 人を表すクラス
    /// </summary>
    public class Person
    {
        /// <summary>
        /// 名前
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 指定されたメッセージで挨拶する
        /// </summary>
        /// <param name="message">メッセージ</param>
        public void Greet(string message)
        {
            Console.WriteLine(message);
        }
    }

しかし、そのDLLファイルを参照させた別のプロジェクトから利用する際に、メソッドのコメントがインテリセンスで表示されません。
image.png

デバッグ実行時にステップインができない

デバッグ実行時に、参照しているDLLのクラスのメソッドにステップインしたいと思っても、ステップインできません。

お勧めする便利な設定

以下の設定を行うことで、両方の問題が解消できます。

インテリセンスでコメントが表示されるようにする

-doc (C# コンパイラ オプション) を付けることで、XML ファイル内にドキュメント コメントを含めることができます。
具体的には、以下の手順でプロジェクト設定を変更すると、ビルド時にDLLファイルと同じフォルダにXMLファイルが作成されます。

  1. プロジェクトの [プロパティ] ページを開きます。
  2. [ビルド] タブをクリックします。
  3. [XML ドキュメント ファイル] プロパティにチェックを付けます。

-doc の詳細は以下を参照ください。
-doc (C# コンパイラ オプション) | Microsoft Docs

上記で作成したXMLファイルを、参照するDLLファイルと同じフォルダに格納しておけば、DLLファイルの読み込み時に、XMLも一緒に読み込んでくれます。その結果、下図のように、インテリセンスでコメントも表示されるようになります。
image.png

デバッグ実行時にステップインできるようにする

デバッグ実行するためには、ビルド時にDLLと一緒に生成される pdbファイルが必要になります。また、Releaseビルドのファイルではデバッグ実行できないため、Debugビルドのファイルに差し替える必要があります。
従って、DebugビルドしたDLLとpdbファイルを同じフォルダに格納し、そのDLLファイルを参照させることで、そのDLLのメソッドをステップ実行できます。

詳細は以下を参照ください。
Visual Studio デバッガーでシンボル (.pdb) ファイルとソース ファイルの指定 (C#、C++、Visual Basic、 F#) | Microsoft Docs

下図は、DLLファイルを参照したプロジェクトから、DLL内のクラスのメソッドにステップインした時の画面です。ソースコードのコメントや行番号も確認できます。
image.png

なお、テスト実施やリリースの際には、Release ビルドのDLLを利用する必要があるため、別途 ReleaseビルドのDLLを別フォルダに格納しておくことをお勧めします。インストーラを作成するスクリプトなどは、ReleaseビルドのDLLを利用するように設定しておくと良いと思います。

まとめ

DebugビルドしたDLLとXMLとpdbのファイルを同じフォルダに格納した上で、参照させると便利です。

私は上記手法を用いて こちらのツール を作っています。

Twitterでも開発に役立つ情報を発信しています → @kojimadev

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

ほふれ!いんたーふぇいす!

動機

たまにIDEで参照をたどろうとしたらInterfaceだったからたどれないー嫌いだーという話をたまに耳にします。

なのでそのInterfaceは本当に必要なのか、必要ないなら外してしまおう!ほふってしまおう!と思い書きました。

たどってみよう

ひとまずInterfaceをほふる前にたどってみましょう。

Interface.cs
public interface Interface
{
    void Print();
}
Implements.cs
public class Implements : Interface
{
    public void Print()
    {
        Debug.Log("print by implements.");
    }
}
TestScene.cs
public class TestScene
{
    void Main()
    {
        var implements = new Implements();
        Use(implements);
    }

    // どんな実態が入ってくるか分からないが、Interface越しに共通のメソッドを叩ける
    void Use(Interface i)
    {
        i.Print();
    }
}

こんなクラスたちを用意します。

UseメソッドのPrintの実装を探してみます。
VSCodeだとGo to Definitionで定義まで飛べますね。
スクリーンショット 2020-03-15 23.48.48.png

しかし、Interfaceなので実装まで飛べません。
なんということでしょう。つらいですね。

スクリーンショット 2020-03-16 0.37.58.png

ん?

すぐにどんな処理かわかりませんね...
実装までもう少しです。
おや、Go to Implementationsで実装先まで飛べますね。
頑張って探しにいきましょう!

え?

...何かがおかしい。

interfaceとは?

まず前提として今回のサンプルコードでは実装が1つだけです。
数個なら問題ないかもしれません。実装者があなたなら振る舞いからどのクラスかも特定できそうですね。
しかし、他人の書いたコードであったり、10個とかあった場合にひとつひとつGo to Implementationsでたどっていくのは苦行です。

あなたのコードはどうでしょうか?
なぜinterfaceを使ったのですか?
汎用性を持たせてinterfaceにしたと思いますが、もちろん複数の実装があるからですよね?

  • 複数の実装があり動的に決定したい
  • interfaceが決まっているので量産してる
  • レイヤーを分けているから実装を入れたくない

などinterfaceを使う理由があると思います。

つまり、「使っている側」からは実装された実態が分からないのは当たり前です。
「使っている側」は中身を知らなくても共通で定義された決まりごと(インターフェイス)を元にその処理を使うことができるのが旨味なわけです。

(そもそも、IDEの静的解析では無理だと思います。Aボタンが押された時はaクラスが渡ってきて、Bボタンが押された時はbクラスが渡ってくるように、実行時に動的に切り替えれるのが旨味です)

「実装の中身を見るためにたどりたい」と思った時に、たどる方向が逆じゃありませんか?
interface越しに「使っている側」からIDEでたどりたいということは、その実装が一意に定まっていなければ不可能なはずです。
そうでありたいのならばinterfaceを使わずに実態をそのまま持てばいいはずです。

たどりたいなら意識を向けるべきは、もう少し手前側で、その周辺プログラムが動作してどのような実態が渡ってきているのかです。

先ほどのコードではこの部分ですね。
Interfaceを実装したImplementsというクラスを生成して渡しています。

TestScene.cs
var implements = new Implements();
Use(implements);

見るべきは決してログが出ていたこの部分ではありません。

TestScene.cs
void Use(Interface i)
{
    i.Print();
}

今回は簡易サンプルなのでUseを呼び出す直前で実態がお見えしておりますが、もっと上位の遠くにあるかもしれません。
レイヤーを分けて上位の実態を持たないようにしている場合によくありますね。
でも、interfaceは共通で決まっているのだから、探したいのはどんな実態が来ているかということではないですか?

例え話

プログラムから一度離れて、誰もが知っているインターフェイス。
それはUSB!

現在のパーソナルコンピュータ周辺機器において、最も普及した汎用インターフェース規格である。
Wikipedia

ハードウェアはあまり詳しくないので見当違いな例でしたら申し訳ありません。
記憶媒体のUSBメモリについて考えてみます。共通の通信規格を持つことで、いろんなメーカーが作ってもUSB差し込めるパソコンなら読み込めますね。

あなたはUSBメモリ検品係です。
10個のUSBメモリを同時に挿してチェックしています。
1つエラーが発生するものがありました。
エラーが発生するUSBメモリを見つけ出し、担当の人に渡さなければなりません。

しかし、ファイルエクスプローラーからそのUSBを覗いてみてもわかりません。
ドライブD/E/F/G/...、どのドライブがどのUSBメモリかもわかりません。

探すためには差込口にどれが差し込まれているのか、一個一個外して探していく...

とかなんか例を考えたらどうしようもない話になったのでここで筆を止めます。

まとめ

interface含め、利点があるから使っているはずです。
その利点が欠点になっているのなら使い方を見直してはどうでしょうか?

今回の例では、interfaceから実態が探せないという例でした。
一意に定まっていいのなら実態をそのまま渡しましょう。
変に「汎用化がー」といって利点を考えずにinterfaceにひとまずしておくというのはやめましょう。ほふりましょう。

汎用化してどんな実態が渡ってきているのか分からない、それをたどりたいんだ!というのなら、どんな実態が渡ってきているのかを探しにいきましょう。

interfaceを用いて、動的に実態が変わるプログラムを書いているならば、おそらく重要なのはどんなフローを経てどんな実態が生成され、渡ってきているのか、のはずです。

小言

interfaceは便利です。個人的には結構使います。

interfaceの欠点とは少しズレている話を耳にしたので書いてみました。

汎用的にメソッドを呼び出すために継承による動作切り替えもあると思います。短いエンジニア経験ではありますが、綺麗に継承を使いこなしている人は見たことがありません。ほとんどの場合、失敗して絡み合った合成獣が誕生しています。

親クラスで状態変数に触るメソッドを勝手に呼び出したりしてぐっちゃぐちゃになる継承よりは...とは思っているのですが、それはinterfaceとしての本来の目的とは違うかもですね。

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

UiPath Activity Creatorを使ってみる(その1:導入)

概要

 このドキュメントはUiPath Activity Creatorの使い方に関するものです。もともと昨年リリースされたバージョンを使ってカスタムアクティビティを作成した経験がありましたが、この3月にちょうどハッカソン参加者向けのWebinarがあったこともあり、最新版ベースの内容でまとめております。なお特に断りのない限り2020年3月時点のバージョンに基づき記載しております。

UiPath Activity Creatorとは?

 UiPath Activity CreatorはUiPathのカスタムアクティビティを簡単に作成するためのVisual Studio Extensionです。以前はUiPath Activity Setという名前でしたが、機能強化とともにUiPath Activity Creatorとなりました。2020年3月現在のバージョンでは、Visual Studio 2019用となっております。(2017以下では動作しません。)
 以下のUiPath Connect!のマーケットプレイス上のページに最新情報等があります。
https://connect.uipath.com/ja/marketplace/components/activity-set-creator

UiPath Activity Creatorの主な機能とこれが必要となる人

 UiPathのカスタムアクティビティの作成方法には主に2つの方法があります。
 ひとつはUiPath Studioの機能として用意されているライブラリ機能を使う方法です。この機能はUiPathのワークフローをそのままカスタムアクティビティにできますので、単純な要件のものであれば、こちらで事足るケースも多いと思われます。
 もう一つはVisual Studio等の開発ツールを用いて、作成する方法です。こちらはライブラリ機能と比べると、UiPath (Studio)の制限にとらわれることなく、自由に機能を実装できます。しかしながらその実装には、C#といった言語知識や、UiPathがベースとしているWindows Workflow Foundationの知識等が必要になってきます。そのため、一般的には相応の高度な知識が必要とされます。
 UiPath Activity Creatorは後者の実装方法に対するハードルを下げるためのVisual Studio Extensionになります。具体的には以下のようなことが準備されています。
- アクティビティの各種設定(単体 or Scopeやプロパティ等)が可能なWizard
- 非同期動作が可能かつ利便性を高めた独自クラス
- 多言語化(国際化)可能な設計
- Designer周りの設定・ファイル
- nupkg周りの設定・ファイル

 非同期処理や高度なデザインといった機能が必要な場合は、Visual Studioでの開発が必要になりますが、UiPath Activity Creatorの利用により生産性向上や技術的ハードルを下げることにつながるかと思います。逆にこのような機能が必要ないケースは、あえてこれを使う必要はなく、Studioのライブラリ機能で十分かもしれません。

UiPath Activity Creatorの導入

  1. Visual Studioのメニューから拡張機能→拡張機能の管理を開きます。
  2. Visual Studio Marketplaceが選択されている状態で、検索窓にUiPathと入力します。
  3. 下図のようにUiPath Activity Creatorがヒットしますのでこれを導入します。 uac1-1.png

これで導入は完了です。

プロジェクトの作成

導入が完了したら、さっそくプロジェクトを作成してみましょう。
Visual Studio 2019のスタート画面から「新しいプロジェクトの作成」を選択します。
uac1-2.png

次に適用するテンプレートを選択します。右上の検索窓から UiPath と入力すると、UiPath Standard Activity Projectがヒットしますので、これを選択します。
uac1-3.png

プロジェクト名を入力して、完了です。
uac1-4.png

最終的にプロジェクト内に、下図のようなプロジェクトの骨格と、共有ライブラリが配置されます。
uac1-5.png

Wizardの利用

 前章で導入したテンプレートだけでも、機能面としてはかなり強力なのですが、さらに利用に際してのハードル低下や生産性向上を実現するためのWizardが用意されています。
 ソリューションエクスプローラーで、作成したプロジェクト名配下のファイル(Sharedのフォルダではないファイル)にフォーカスが当たっている状態で、拡張機能→UiPath→Add Activityを選択します。

uac1-6.png

下図の通り、CreateかImportを選択する画面が出てきます。
uac1-7.png

最初なのでCreateを選択します。
下図のアクティビティの設定画面が出てきます。追加する場合は+マークをクリックします。

uac1-8.png

入力可能になりますので、必要事項を入力していきます。
なおTypeの部分はSimpleとScopeが選択できます。
uac1-9.png

Propertyの編集ボタンをクリックすると以下の画面が出てきますので、そのアクティビティのプロパティの定義を入力していきます。
uac1-A.png

入力が終わったら右下のFinishをクリックします。
uac1-B.png

以下のように自動的にコードが生成されます。
uas1-C.png

ロジックは自分で記述する必要がありますので、これをベースに記述していきます。

次回以降は高度な機能の実装について記述したいと思います。

(その1終わり)

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