20210122のC#に関する記事は16件です。

Slackで特定のメッセージを取得するby C#

はじめに

Slack APIで自動でメッセージを投稿して、
そのメッセージを別のチャンネルにメッセージを引用したので
その覚書

やりたいこと

chat.postMessage APIを利用して投稿した値のリクエストに対するレスポンスからtsを取得。

その後、chat.getPermalink APIのmessage_tsとして設定することにより
レスポンスとしてメッセージURLを取得できる。
その値をchat.postMe APIで投稿する。

今回はchat.getPermalink APIで設定する部分を紹介する。

公式ドキュメントは以下。
必要なあるいは設定できる引数やレスポンスの形式が記載されているので
APIを使う前に目を通した方が良い。

・chat.postMessage
https://api.slack.com/methods/chat.postMessage

・chat.getPermalink
https://api.slack.com/methods/chat.getPermalink

ソース

public class Responce
{ 
  public string permalink{get; set; }
}
public string Main()
{
  var parameters = new NameValueCollection();

  //クエリ作成
  parameters.ADD("token",["トークン"]);
  parameters.ADD("channel",["チャンネルID"]);
  parameters.ADD("message_ts",["取得したいメッセージのts"]);

  using(var client = new WebClient())
  {
    //リクエストを送信し、レスポンスを取得
    byte[] responceByte = client.Upload.Values("https://slack.com/api/chat.getPermalink",parameters)

    string responceStr = Encoding.UTF8.GetDtring(responceByte);
    Responce responce = JsonConvert.DeserializeObject<Responce>(responceStr);

    //取得したメッセージのURLを返す
    return (responce.permalink);

}

Content Type: application/x-www-form-urlencodedの形式で
トークン、チャンネルID、tsを引数としてリクエストを送信してやれば良い。

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

【C#入門】初学者がASP.NETでWebアプリを作る:第3回

今回やること

・登録処理の作りこみ
・アカウント認証機能の追加

登録処理の作りこみ

現状の問題点

①初期値がない(金額は0、日付は今日が入っててくれた方が私はうれしい。空文字になっちゃう…。)
②区分(給与/賞与)が自由入力になっているのでコンボボックスにしたい
③備考欄が小さすぎるのでいっぱい書けるように見えるようにでかくしたい
④管理項目(登録ユーザID、登録日時、更新ユーザID、更新日時)がコードビハインドで設定しなければいけない
image.png
image.png

①初期値がない→初期値を定義する

支給日はModelでDataTypeとフォーマットを指定します。

Salary.cs
        // 支給日
        [Display(Name = "支給日")]
        [DisplayFormat(DataFormatString ="{0:yyyy-MM-dd}")] //フォーマットの指定
        [DataType(DataType.Date)]                           //DataTypeの指定
        [Required(ErrorMessage = "支給日は必須入力です")]
        public DateTime PaymentDate { get; set; }

Create.cshtmlの各項目にvalueを定義します。

Create.cshtml
@model SalaryManagementSystem.Models.Salary

@{
    ViewData["Title"] = "データ登録";
}

<h1>データ登録</h1>

<h4>給与/賞与情報</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Beneficiary" class="control-label"></label>
                <input asp-for="Beneficiary" class="form-control" />
                <span asp-validation-for="Beneficiary" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="PaymentType" class="control-label"></label>
                <input asp-for="PaymentType" class="form-control" value="給与"/>
                <span asp-validation-for="PaymentType" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="PaymentDate" class="control-label"></label>
                <input asp-for="PaymentDate" class="form-control" value=@DateTime.Now.ToString("yyyy-MM-dd") />
                <span asp-validation-for="PaymentDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="PaymentAmount" class="control-label"></label>
                <input asp-for="PaymentAmount" class="form-control" value="0"/>
                <span asp-validation-for="PaymentAmount" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="TravelExpence" class="control-label"></label>
                <input asp-for="TravelExpence" class="form-control" value="0"/>
                <span asp-validation-for="TravelExpence" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="HealthInsurancePremium" class="control-label"></label>
                <input asp-for="HealthInsurancePremium" class="form-control" value="0"/>
                <span asp-validation-for="HealthInsurancePremium" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="WelfarePension" class="control-label"></label>
                <input asp-for="WelfarePension" class="form-control" value="0"/>
                <span asp-validation-for="WelfarePension" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="EmploymentInsurancePremium" class="control-label"></label>
                <input asp-for="EmploymentInsurancePremium" class="form-control" value="0"/>
                <span asp-validation-for="EmploymentInsurancePremium" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="IncomeTax" class="control-label"></label>
                <input asp-for="IncomeTax" class="form-control" value="0"/>
                <span asp-validation-for="IncomeTax" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ResidentTax" class="control-label"></label>
                <input asp-for="ResidentTax" class="form-control" value="0"/>
                <span asp-validation-for="ResidentTax" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="TotalPaymentAmount" class="control-label"></label>
                <input asp-for="TotalPaymentAmount" class="form-control" value="0"/>
                <span asp-validation-for="TotalPaymentAmount" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="OvertimeAllowance" class="control-label"></label>
                <input asp-for="OvertimeAllowance" class="form-control" value="0"/>
                <span asp-validation-for="OvertimeAllowance" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="MidnightAllowance" class="control-label"></label>
                <input asp-for="MidnightAllowance" class="form-control" value="0"/>
                <span asp-validation-for="MidnightAllowance" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="HolidayAllowance" class="control-label"></label>
                <input asp-for="HolidayAllowance" class="form-control" value="0"/>
                <span asp-validation-for="HolidayAllowance" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Remarks" class="control-label"></label>
                <input asp-for="Remarks" class="form-control" />
                <span asp-validation-for="Remarks" class="text-danger"></span>
            </div>
            @*<div class="form-group">
            <label asp-for="RegisterDate" class="control-label"></label>
            <input asp-for="RegisterDate" class="form-control" />
            <span asp-validation-for="RegisterDate" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="RegisterUser" class="control-label"></label>
            <input asp-for="RegisterUser" class="form-control" />
            <span asp-validation-for="RegisterUser" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="UpdateDate" class="control-label"></label>
            <input asp-for="UpdateDate" class="form-control" />
            <span asp-validation-for="UpdateDate" class="text-danger"></span>
        </div>
        <div class="form-group">
            <label asp-for="UpdateUser" class="control-label"></label>
            <input asp-for="UpdateUser" class="form-control" />
            <span asp-validation-for="UpdateUser" class="text-danger"></span>
        </div>*@
            <div class="form-group">
                <input type="submit" value="登録" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">一覧へ戻る</a>
</div>

支給日は下記のように定義することで今日の日付を設定するようにしています。

value=@DateTime.Now.ToString("yyyy-MM-dd")

②区分(給与/賞与)をコンボボックスにする

inputタグを下記の通りselectタグにします。
デフォルトは給与にしています。

<label asp-for="PaymentType" class="control-label"></label>
<!--<input asp-for="PaymentType" class="form-control" value="給与"/>-->
<select asp-for="PaymentType" class="form-control" value="給与">
    <option value="給与" selected>給与</option>
    <option value="賞与">賞与</option>
</select>
<span asp-validation-for="PaymentType" class="text-danger"></span>

③備考欄が小さすぎるので大きくする

備考欄のstyleで指定しました。

<label asp-for="Remarks" class="control-label"></label>
<input asp-for="Remarks" class="form-control" style="width: 500px; height: 100px"/>
<input asp-for="Remarks" class="form-control" width="500px" height="100px"/> ←これでは有効にならない
<span asp-validation-for="Remarks" class="text-danger"></span>

④管理項目をコードビハインドで設定する

SalariesController.csのPOSTで呼び出されるCreateメソッドに追記しました。

SalariesController.cs
// POST: Salaries/Create
        // To protect from overposting attacks, enable the specific properties you want to bind to.
        // For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("Id,Beneficiary,PaymentType,PaymentDate,PaymentAmount,TravelExpence,HealthInsurancePremium,WelfarePension,EmploymentInsurancePremium,IncomeTax,ResidentTax,TotalPaymentAmount,OvertimeAllowance,MidnightAllowance,HolidayAllowance,Remarks,RegisterDate,RegisterUser,UpdateDate,UpdateUser")] Salary salary)
        {
            // 追加ここから
            salary.RegisterDate = DateTime.Now;
            salary.RegisterUser = "DUMMY";
            salary.UpdateDate = DateTime.Now;
            salary.UpdateUser = "DUMMY";
            // 追加ここまで
            if (ModelState.IsValid)
            {
                _context.Add(salary);
                await _context.SaveChangesAsync();
                return RedirectToAction(nameof(Index));
            }
            return View(salary);
        }

登録操作をしてみる

上記の調整で、登録画面の初期状態はこのようになりました。
image.png
image.png

適当に入力します。
image.png
image.png
(備考欄を上寄せにしたい。。。)
登録ボタンを押します。

登録できました。表示がイケてない。。。まあ一覧画面はこんなに情報を出す気はないので後で直しましょう。
image.png

詳細を押すと詳細画面に遷移します。
image.png

編集画面にも遷移できます。編集画面はまだ表示の調整をしていませんでしたので、管理項目が登録できていたことがわかります。
image.png

DBを直接確認しても、きちんとデータが保存されています。
image.png

次回予告

アカウント認証を追加します。

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

C#でAtCoder Beginners Selection(ABC085C - Otoshidama)

準備

C#でAtCoderデビューのための準備
のあとで AtCoder Beginners Selection をやってみました。

問題文

ABC085C - Otoshidama
https://atcoder.jp/contests/abs/tasks/abc085_c

提出結果

using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        long[] array = Console.ReadLine().Split().Select(long.Parse).ToArray();
        long N = array[0];
        long Y = array[1] / 1000;

        long x = -1;
        long y = -1;
        long z = -1;
        bool ari = false;
        for (long ix = N; ix >= 0; ix--)
        {
            for (long iy = N - ix; iy >= 0; iy--)
            {
                long iz = N - ix - iy;
                if ((10 * ix) + (5 * iy) + iz == Y)
                {
                    x = ix;
                    y = iy;
                    z = iz;
                    ari = true;
                    break;
                }
            }
            if (ari) break;
        }
        Console.WriteLine($"{x} {y} {z}");
    }
}

テスト実行

image.png
image.png
image.png
image.png

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

VisualStudioのXMLコメントがなぜか表示されない場合の対処方法

なぜかVisualStudioのXMLコメントが正常に動作しない場合がありました。
コメントを書いているのに、マウスをポイントしたときに書いたコメントが表示されない。
image.png

原因は<でした。
<, >はXMLのタグの一部として使われるため、コメント内にこれらの記号を入れると正常に動作しなくなる模様。

じゃあどうすればいいかと言うと、<の代わりに&lt;を、>の代わりに&gt;を用いる。

image.png

正常にXMLコメントが表示されました。

このことは、MSDocsにも書かれています。

ドキュメント コメントのテキストに山かっこを表示する場合は、< と > の HTML エンコードを使用します。これはそれぞれ、< と > になります。 このエンコードは次の例に示されています。

ドキュメント コメント用の推奨タグ (C# プログラミング ガイド)

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

C#でAtCoder Beginners Selection(ABC085B - Kagami Mochi)

準備

C#でAtCoderデビューのための準備
のあとで AtCoder Beginners Selection をやってみました。

問題文

ABC085B - Kagami Mochi
https://atcoder.jp/contests/abs/tasks/abc085_b

提出結果

using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        int n = int.Parse(Console.ReadLine());
        var d = Enumerable.Repeat(0, n).ToArray();

        for (int i = 0; i < n; i++)
        {
            d[i] = int.Parse(Console.ReadLine());
        }
        Array.Sort(d);
        int dan = 1;
        for (int i = 1; i < n; i++)
        {
            if (d[i] > d[i -1])
            {
                dan++;
            }
        }
        Console.WriteLine($"{dan}");
    }
}

テスト実行

image.png
image.png
image.png

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

[C#] 参照型における()キャストとis, asキーワードによる型変換の速さの違い

はじめに

C#で型変換に使う主な5つの方法の速さを比べました。

結論

()キャストを使う場合、例外が発生すると極端に遅くなるが、全く例外が発生しないケースでは最速でした。
is, asの型変換はどんなケースでもそれなりの速さが出るようです。

環境

Windows 10 Home, AMD Ryzen 5 3500 6-Core Processor(3.59 GHz), 16GB Memory, Visual Studio 2019, .Net Core 3.1

計測コード

class A
{
    public A(string s)
    {
        member = s;
    }

    string member;
}

class B
{
    public B(string s)
    {
        member = s;
    }

    string member;
}

//tryとcatchで囲って()キャストする方法
static void Cast(object[] vs)
{
    foreach (object o in vs)
    {
        try
        {
            A v = (A)o;
        }
        catch
        {

        }
    }
}

//asキーワードで変換してからnullチェックする方法
static void AsIf(object[] vs)
{
    foreach (object o in vs)
    {
        A v = o as A;
        if (v == null) { }
    }
}

//isキーワードで変換できるかをチェックし、そのまま変換する方法
static void IfIs(object[] vs)
{
    foreach (object o in vs)
    {
        if (o is A v) { }
    }
}

//isキーワードでチェックしてから()キャストする方法
static void IfIsCast(object[] vs)
{
    foreach (object o in vs)
    {
        if (o is A)
        {
            A v = (A)o;
        }
    }
}

//isキーワードでチェックしてからasキーワードで変換する方法
static void IfIsAs(object[] vs)
{
    foreach (object o in vs)
    {
        if (o is A)
        {
            A v = o as A;
        }
    }
}

//5つの変換方法を試す
static void TryMethods(int percentageOfA)
{
    int separator = (int)(percentageOfA / 100f * ss.Length);
    object[] vs = ss.Select((s, i) => i < separator ? (object)new A(s) : new B(s)).ToArray();

    Console.WriteLine($"{percentageOfA}%");
    Console.WriteLine($"Cast:\t\t{Measure(count, () => Cast(vs))}");
    Console.WriteLine($"AsIf:\t\t{Measure(count, () => AsIf(vs))}");
    Console.WriteLine($"IfIs:\t\t{Measure(count, () => IfIs(vs))}");
    Console.WriteLine($"IfIsCast:\t{Measure(count, () => IfIsCast(vs))}");
    Console.WriteLine($"IfIsAs:\t\t{Measure(count, () => IfIsAs(vs))}");
    Console.WriteLine();
}

static int count = 3000;
static string[] ss = new string[10000000];

//エントリポイント
public static void Entry()
{
    Random random = new Random();
    for (int i = 0; i < ss.Length; i++)
    {
        int length = random.Next(10);
        for (int j = 0; j < length; j++)
        {
            ss[i] += (char)random.Next('A', 'z');
        }
    }

    Console.WriteLine($"count:\t{count}");
    Console.WriteLine($"length:\t{ss.Length}");
    Console.WriteLine();

    TryMethods(0);
    TryMethods(20);
    TryMethods(40);
    TryMethods(60);
    TryMethods(80);
    TryMethods(100);
}

//関数の処理にかかる時間を計測する
public static TimeSpan Measure(int count, Action action)
{
    DateTime time = DateTime.Now;
    for (int i = 0; i < count; i++)
    {
        action();
    }
    return DateTime.Now - time;
}

結果

型変換に失敗する参照が含まれている場合

例外処理が重すぎたため、以下の数値設定でひとまず測りました。
count = 1
length = 10000
表の左上の値は型変換に失敗する参照が含まれている割合です。
時間の単位は秒です。

0% 1回目 2回目 3回目 平均
Cast 71.1655179 71.8009120 71.6555776 71.5406692
AsIf 0.0002877 0.0003214 0.0002501 0.0002864
IfIs 0.0002317 0.0001495 0.0001622 0.0001811
IfIsCast 0.0001465 0.0001404 0.0002372 0.0001747
IfIsAs 0.0001366 0.0001362 0.0001423 0.0001384
20% 1回目 2回目 3回目 平均
Cast 56.6513413 56.5845480 57.0564668 56.7641187
AsIf 0.0000406 0.0000440 0.0000363 0.0000403
IfIs 0.0000344 0.0000424 0.0000406 0.0000391
IfIsCast 0.0000363 0.0000399 0.0000358 0.0000373
IfIsAs 0.0000407 0.0000351 0.0000353 0.0000370
40% 1回目 2回目 3回目 平均
Cast 42.7042395 42.6855280 42.9887469 42.7928381
AsIf 0.0000365 0.0000365 0.0000361 0.0000364
IfIs 0.0000364 0.0000376 0.0000324 0.0000355
IfIsCast 0.0000342 0.0000369 0.0000368 0.0000360
IfIsAs 0.0000425 0.0000392 0.0000337 0.0000385
60% 1回目 2回目 3回目 平均
Cast 28.6295313 28.4973504 28.7604727 28.6291181
AsIf 0.0000340 0.0000340 0.0000343 0.0000341
IfIs 0.0000317 0.0000315 0.0000325 0.0000319
IfIsCast 0.0000490 0.0000369 0.0000372 0.0000410
IfIsAs 0.0000315 0.0000306 0.0000311 0.0000311
80% 1回目 2回目 3回目 平均
Cast 14.4235740 14.4582490 14.4788299 14.4535510
AsIf 0.0000315 0.0000325 0.0000309 0.0000316
IfIs 0.0000273 0.0000329 0.0000315 0.0000306
IfIsCast 0.0000304 0.0000342 0.0000342 0.0000329
IfIsAs 0.0000322 0.0000363 0.0000304 0.0000330
100% 1回目 2回目 3回目 平均
Cast 0.0000244 0.0000241 0.0000264 0.0000250
AsIf 0.0000286 0.0000272 0.0000259 0.0000272
IfIs 0.0000263 0.0000262 0.0000261 0.0000262
IfIsCast 0.0000303 0.0000358 0.0000300 0.0000320
IfIsAs 0.0000303 0.0000302 0.0000301 0.0000302

型変換に失敗する参照が含まれていない場合

上の結果でいう100%だけを以下の数値設定で測りました。
count = 3000
length = 10000000

100% 1回目 2回目 3回目 平均
Cast 73.9517405 74.0026143 74.1104895 74.0216148
AsIf 81.3793428 81.5171491 81.4947477 81.4637465
IfIs 81.9090815 82.2008835 81.9648811 82.0249487
IfIsCast 93.9863807 94.0810441 87.0868509 91.7180919
IfIsAs 93.9113493 94.2150005 87.0688733 91.7317410

()キャストは最速といっても気持ち速い程度のようです。
IfIsCastとIfIsAsは値が比較的安定していないので、実行状況によって速さが変わりやすいのかもしれません。

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

[Unity]VSCodeでC#インテリセンスが効かず、 "Cannot start OmniSharp because Mono version >=x.y.z is required."と表示された場合の対処法

初投稿です。
ちょっと変なとこで躓いてイラッとしたので憂さ晴らしに記事を書くことにしました。

monoの最新バージョンをbrew install monoなりでインストールしてる前提です。

VSCode内 「Ctrl+Shift」
setting.json内の以下パスを確認。

"omnisharp.monoPath":/usr/local/Cellar/mono/x

ターミナルで以下を実行。

$ls /usr/local/Cellar/mono/

現在のmono最新は"6.12.0.90"なんですけど、こんな感じの文字が出力されます。
出力された文字とxと一致していなかった場合は、
setting.jsonのx部分を出力文字で置き換えてください。

VSCode内 「Ctrl+Shift+P」

>omnisharp: restart omnisharp

これでインテリセンスなり参照なりできるようになりました。

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

EntityFrameworkCoreでDDLファイルを出力する

目的

ASP.NET Core(.NET Core)でコードファーストでデータベースを作成する。
この際、実際データベースに発行しているDDL(SQL)をファイルに出力する方法を記す。

前提条件

Microsoft.EntityFrameworkCore.Tools を導入済である

方法

1.テーブルセットファイルを作成する
PM> Add-Migration Init
Build started...
Build succeeded.
To undo this action, use Remove-Migration.

https://docs.microsoft.com/ja-jp/ef/core/cli/powershell#add-migration

2.SQLスクリプト(DDL)を作成する
PM> Script-Migration 0
Build started...
Build succeeded.

https://docs.microsoft.com/ja-jp/ef/core/cli/powershell#script-migration

注意

本来マイグレーションを意識したコマンドである。

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

Chrome拡張機能を作ってみよう③

前回はChrome拡張機能とローカルプログラムの連携までを書きました。
今回はその続きです。

URLの監視

特定のURLにアクセスした場合にメッセージを表示するため
URLの監視を行います。

監視を行うには
manifest.jsonのcontent_scriptsの項目にあるjsで行います。
今回ですと「message.js」です。

message.js
/*
    ■ URL監視
*/
$(function() {
  try{
    // background.jsにurlを送る
    chrome.runtime.sendMessage({method: 'checkurl', url: document.URL},
    errorHandle(function(response1) {
      // 必要ないけどとりあえず受け取る
      var message = response1.data;
    }));
  }catch(e){
    console.error(e);
  }
});

/**
 * 例外をまとめて処理する
 */
function errorHandle(process) {
    return function(){
      try {
        return process.apply(this, arguments);
      } catch (e) {
        chrome.browserAction.setIcon({path:"images/abnormal.png"});
        console.error(e);
      }
    };
  }

message.jsから受け取るためbackground.jsを以下の内容に修正しています。

background.js
/*
  初期起動時の処理
*/
// インストール時かバージョンアップ時
chrome.runtime.onInstalled.addListener(function() {
  initialize();
});

// ブラウザ起動時
chrome.runtime.onStartup.addListener(function() {
  initialize();
});


function initialize() {
  // ファイルダウンロード先
  var dlFileName = "http://localhost/sample.txt";
  // ファイルを取得
  $.ajax({
    url: dlFileName,
    type: "GET",
    dataType: 'binary',
    responseType:'arraybuffer',
    timeout: 500
  })
  // 成功時
  .done(errorHandle(function (response) {
    var data = response;
    // ArrayBufferで取得するので
    var textfiledata = String.fromCharCode(...new Uint8Array(data));
    console.log(textfiledata);
    // 渡す前にbase64エンコードしておく
    textfiledata = btoa(textfiledata); 
    // ダウンロードした内容をprogramに連携する
    chrome.runtime.sendNativeMessage('put.message',
    {Action: "putmessage", Data: textfiledata},
    errorHandle(function(response, thread){
      // デコードしてJSON形式にする
      var getData = JSON.parse(decodeURIComponent(response));
      // 受け取ったコードがエラーの場合
      if(getData.Code != 0){
        throw new Error('programでエラーが発生');
      }
      // 受け取ったデータをlocalStorageに保存しておく
      localStorage.setItem('urllist', atob(getData.Data));
      return true;
    }));
    // 成功した場合は拡張機能のアイコンを切り替える
    chrome.browserAction.setIcon({path:"images/success.png"});
    // localStorageにステータスを保存
    localStorage.setItem('Status', 'ok');
  }))
  // 失敗時
  .fail(errorHandle(function () {
    // localStorageにステータスを保存
    localStorage.setItem('Status', 'ng');
  }));
  return true;
}

/**
 * Chrome拡張全体用
 */
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  switch (request.method) {
    // マニフェストファイルの内容を取得する
    case 'getManifest':
      var manifest = chrome.runtime.getManifest()
      sendResponse({data: manifest})
      break;
    // URLを受け取る
    case 'checkurl':
      var url = request.url;
      // urllistの内容と一致するか確認する
      localStorage.setItem('match', 'URLが一致しません。');
      var urllist = JSON.parse(localStorage.getItem('urllist'));
      for(var key of Object.keys(urllist)){
        if(url == urllist[key]){
          // 含んでいる場合
          localStorage.setItem('match', 'URLが一致します。');
          sendResponse({data: "URLが一致します。"});
          break;
        }
      }
      break;
    case 'getApInfo':
    default:
      console.log('no method:' + request.method);
      break;
  }
  return true;
});
/**
 * 例外をまとめて処理する
 */
function errorHandle(process) {
  return function(){
    try {
      return process.apply(this, arguments);
    } catch (e) {
      chrome.browserAction.setIcon({path:"images/abnormal.png"});
      console.error(e);
    }
  };
}

あとはメッセージを表示するためpopup系を修正します。

popup.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-cache">

    <link href="../css/popup.css" rel="stylesheet">

    <script src="../js/jquery-3.4.1.js" ></script>
    <script src="../js/popup.js" ></script>
  </head>

  <body id="body">
  <div class="box">
    <div class="header">
      <h3 style="margin: 5px 0">動作状況</h3>
    </div>
    <div>
      <p id="status"></p>
    </div>
    <div>
      <p id="match"></p>
    </div>
  </div>
  <div class="footer">
    <div id="productver"></div>
  </div>
  </body>
</html>
popup.js
$(function() {
  /*** localStorageからステータスを取得。 ***/
  var Status = localStorage.getItem('Status');
  var match = localStorage.getItem('match');
  if(Status == 'ok'){
    $('#status').html('<b>正常</b>');
    $('#match').html(match);
  }else{
    $('#status').html('<b>異常</b>');
  }
  // 特に意味はないけどマニフェストファイルのバージョン情報を取得する
  chrome.runtime.sendMessage({method: 'getManifest'},
  function(response) {
    var manifest = response.data;
    $('#productver').text(manifest.name + ' ver.' + manifest.version);
  });
});

拡張機能の作成については以上になります。
次回はChrome拡張機能のインストールについて投稿します。

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

[C#]例外処理の利用方針があやふやだったので、勉強しなおした

はじめに

自分は業務でC#を書いているのですが、今までかなりあやふやな知識で例外処理を使っていました。そこで、一度学びなおし、現時点での自分の中での「例外処理の利用方針」を固めておこうと思いました。

学びなおすために色々調べた結論としては、自分の知りたかった例外処理の利用方針は「++C++;」の「[雑記]例外の使い方」のページにとてもわかりやすくまとまっていました。

ただ上のページを読んだうえで自分の言葉でもまとめておきたかったので、上のページを大幅に参考にさせてもらいつつ、自分なりの解釈やコード例などを付け加えてまとめたのがこの記事となります。

  • この記事は個人の考えをまとめたものです。
  • この記事の内容はC#のバージョンについて特に意識していませんが、一部のコード例は古いバージョンのC#では使えない構文や機能を使っている可能性があります。

++C++;で提案されている例外処理の利用方針

++C++;」では大体以下のような方針での例外処理の利用が提案されていました。(自分の選択で抜粋 + 解釈を加えさせてもらってるので、原文そのままの引用になっていません。ちゃんと見たい方は元ページを参照ください。)

例外処理利用の基本ルール

  • 「メソッドの定める結果を達成できないなら例外を投げる」
  • 例外は3パターンあり、それぞれに対して次のような対策方針をとる

例外3パターンとその対策方針

例外のパターン 具体例 対処法
①利用法上の例外(利用者側が正しい使い方をしていれば回避できる例外) ・nullを渡して欲しくないメソッドの引数がnull になってる。(Sytem.ArgumentNullException)
・初期化がきちんと行わる前にクラスの操作が行われた。(System.InvalidOperationException)
・catchしない。発生しなくなるまでテストとデバッグを行う(※)
・できれば事前にチェックが行える仕組みをつくる
②発生は避けれないが復帰可能な例外 ・System.IO.File.ReadAllText (File.Exsits()で事前確認していても、その後の実行までにファイルが消される可能性がある)
・対処可能ならcatchする。
・対処不可能(あるいは上位層に対処を任せた方が良い場合)なら、基本catchしない(※)
③対処のしようがない致命的な例外 ・.NET Framework自体のエラー
・その他プログラムが実行不可能なもの
・catchしない(プログラムを停止させてよい)(※)

(※)「catchしない」というのはcatchして握りつぶしたり、処理の方向を捻じ曲げたりしないという意味。一度catchし、処理途中のもののロールバックやログ出力を行った後、再throwするのは有り。

一言で方針をまとめるなら、「throwは遠慮なくして良いがcatchをするかどうかは状況をしっかり見て決めよう」、といったところでしょうか。

各パターンの詳細

上で見た例外処理3パターンについて、それぞれポイントや具体例などを見ていきます。

パターン①利用法上の例外

利用者側が正しい使い方をしていれば回避できる例外です。
このパターンのポイントは、「catchしない」ことです。

コード例を示します。

void Main()
{
    string name = null;
    // 引数にnullを渡さないことは、メソッド利用側で意識して避けられるのでcatchしない! 
    SayHelloTo(name);
}


void SayHelloTo(string name)
{
    // nullを渡してほしくない引数にnullが入っている。(このままではメソッド定める結果を達成できない)
    if(name is null)
    {
        throw new ArgumentNullException($"{nameof(name)} is null");
    }

    Console.WriteLine($"Hello, {name}");
}

上の例で、メソッド"SayHelloTo()"では、nullを渡して欲しくない引数"name"にnullが入っていた場合に例外をthrowしています。しかしメソッド利用側でcatchはしていません。
catchを書いてないとなんとなくアプリケーションが止まりそうで怖い感じがしますが、ビビッてcatchを書いてはいけません。不正なパターンが起きなくなるまで、しっかりテストを行います。

さらに、このパターンに限らずですが、呼び出される側のメソッド(SayHelloTo())は特に呼び出す側の事を意識する必要はありません。メソッドの処理を正常に行うための前提条件が整っていないなら遠慮なく例外を投げてよいです。
(ただし、頻繁に例外が起きることが予想される場合は、処理コストが高くなるのを避けるため、後述するTry-Parseパターンの実装が好ましいです。)

また、どうしても変数"name"にnullが入る可能性を排除できない場合もcatchは書きません。以下のように、事前チェックをして回避します。

void Main()
{
   string name = null;
   // 引数にnullを渡さないことは、メソッド利用側で意識して避けられるのでcatchしない! 
   // nullチェックはメソッド利用側でできることなので、チェックする。
    if(!(name is null))
    {
        SayHelloTo(name);
    }
    else
    {
        Console.WriteLine($"Cannot execute {nameof(SayHelloTo)}. Because the {nameof(name)} is null)";
    }   
}

void SayHelloTo(string name)
{
    //(実装省略)
}

この際、例外を出さないための事前チェック(上のコード例でいう、!(name is null))がメソッド利用側では難しい場合があります。その場合は、後述のTester-Doerパターンのように、事前チェックをするためのメソッドを用意します。

パターン①利用法上の例外のまとめ

  • メソッドの処理が上手く実行できない場合は、遠慮なくthrowする。
  • ビビッてcatchを書くのではなく、デバッグ・テストを例外がでなくなるまで行う。

パターン②発生は避けれないが復帰可能な例外

例えば簡単なRequestを送るメソッドを考えてみます。

async Task Main()
{
    Uri uri = new Uri($"http://hogehoge/api/message");
    var msg = await GetMessageAsync(uri);
    Console.WriteLine(msg);
}

private static HttpClient client = new HttpClient();

async Task<string> GetMessageAsync(Uri uri)
{
    if (uri is null)
    {
        throw new ArgumentNullException($"{nameof(uri)} is null.");
    }

    // HttpRequestExceptionがthrowされる可能性がある。
    return await client.GetStringAsync(uri);
}

上の例では、"client.GetStringAsync()"でHttpRequestExceptionがthrowされる可能性がありますが、多くの場合これは呼び出す側で事前に予期して避けづらい例外です。

しかしこのエラーは、単にリクエストに失敗したことを表示すれば問題ないので、復帰できるものです。
以下のように復帰しましょう。

async Task Main()
{
    Uri uri = new Uri($"http://hogehoge/api/message");

    try
    {
        var msg = await GetMessageAsync(uri);
        Console.WriteLine(msg);
    }
    // 基本的に具体的な例外クラスをcatchする。catch (Exception ex)とは書かない。
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"Failed to get message. Error message : {ex.Message}");
    }
}

private static HttpClient client = new HttpClient();

async Task<string> GetMessageAsync(Uri uri)
{
    if (uri is null)
    {
        throw new ArgumentNullException($"{nameof(uri)} is null.");
    }

    // HttpRequestExceptionがthrowされる可能性がある。
    return await client.GetStringAsync(uri);
}

特に難しいところはないですね。HttpRequestExceptionを指定でcatchし、それに対する復帰処理(上の例だと失敗メッセージの表示)を行うだけです。

一つポイントとしては、基本的に具体的な例外の種類(Exception派生クラス)をしてやることです。
なぜなら、"catch (Exception ex)"とのように派生元のExceptionクラスを丸ごとcatchしてしまうと、メソッド内でどんなエラーが起きたかがわからず、きちんと復帰できているとは言えないからです。
(ただし、catch句内でExceptionを再throwする、別の種類の例外に変換する(元のExceptionはinnerExceptionにする)といった場合は、派生元のExceptionクラスを丸ごとcatchしてもOKです。)

また、このパターンの悩みどころは、どこで復帰処理を書くかが選べるところです。
上の例では、"Main()"の中でcatchを使い復帰処理を書きましたが、以下のように"GetMessageAsync()"の中で復帰処理を書くこともできます。

async Task Main()
{
    Uri uri = new Uri($"http://hogehoge/api/message");
    var msg = await GetMessageAsync(uri);
    Console.WriteLine(msg);
}

private static HttpClient client = new HttpClient();

async Task<string> GetMessageAsync(Uri uri)
{
    if (uri is null)
    {
        throw new ArgumentNullException($"{nameof(uri)} is null.");
    }

    try
    {
        // HttpRequestExceptionがthrowされる可能性がある。
        return await client.GetStringAsync(uri);
    }
    catch (HttpRequestException ex)
    {
        // このやり方だと、関数呼び出し側で、処理が失敗したかの判断がしづらいので、
        // 関数自体をTry-Parseパターンにした方がよい。
        return $"Failed to get message. Error message : {ex.Message}";
    }
}

上の例では、"GetMessageAsync()"の中で、catchを使い復帰処理を書いています。

このように、このパターン②における復帰処理は、コード中のどの部分でcatchをするかある程度選べてしまいます。メソッドの責務(行いたい処理は何か)を明確にし、適切な呼び出し箇所でcatch,復帰処理を行いましょう。

ちなみに今回の例だと、"Main()"側でcatchを書く方が良いかなと個人的には思います。
なぜなら、上の例で、復帰処理として"Failed ~"という文字列を返していますが、これではメソッド呼び出し側から見て処理が成功したかどうか判別しづらいからです。、
(もし、"GetMessageAsync()"側で復帰処理を行いたい場合は、後述のTry-Parseパターンをつかうなどして処理の成功or失敗をメソッド呼び出し側にわかりやすくする工夫をした方が良いでしょう。)

パターン②に発生は避けれないが復帰可能な例外

  • きちんと復帰できる場合はcatchして復帰してやる。
  • 具体的なException派生クラスをcatchすることを意識する。
  • どこで復帰処理を行うのかが悩みどころ。メソッドの責務を明確にして判断する。

パターン③対処のしようがない致命的な例外

このパターンは特に意識して対処する必要ないと思います。
自分はあまり良い例が思い浮かばないのですが、++C++;-例外の使い方では.NET Framework自体のエラーが例としてあげられていました。

その他の雑多な話

ここまでで基本的な方針は書きました。

ただ実践では理論通り書けない部分も多く、方針があったとしても例外処理の書き方に迷う部分もあると思います。

そこでここからは、例外処理に関連して使うテクニックや、理想通りいかない現実的なコードの話などを、つらつらと書きます。(あまり書く項目に一貫性はないです。)

自分の経験・上の方針をもとに自分が昔書いたコードを修正してみて気づいたこと・C#の有名OSSであるJson.NETのコードを観察して気づいたこと、の3つを内容のベースとして書いています。

Tester-Doerパターン

パターン①で利用法上の例外(≒業務エラー)はcatchせず、メソッド呼び出し側でエラーパターンの事前チェックをすると書きました。しかし、そもそも例外は基本的には発生しない方が望ましいです。
なので、事前チェック用のメソッドを用意してやるのも一つの手です。そのような実装の仕方をTester-Doerパターンと言います。(Tester=事前チェック用メソッド、Doer=実行したいメソッド)

void Main()
{
    string name = "Taro";
    if (CanExcecuteMethodSayHelloTo(name))
    {
        SayHelloTo(name);
    }
}

// Tester
bool CanExcecuteMethodSayHelloTo(string name)
{
    return !(name is null);
}

// Doer
void SayHelloTo(string name)
{
    if (name is null)
    {
        throw new ArgumentNullException($"{nameof(name)} is null");
    }

    Console.WriteLine($"Hello, {name}");
}

この例のnullチェックぐらい簡単な確認ならTesterを用意するまでもないですが。

.NETのクラスでも、このTester-Doerパターンは見られますね。

  • System.Collection.Generics.ICollectionのIsReadOnly
  • System.IO.StreamのCanRead, CanWrite

Try-Parseパターン

この記事のはじめの方に「メソッドの定める結果を達成できないなら例外を投げる」と書きました。
しかし例外は重い処理なので、それなりの発生頻度が予想される場合には使いたくありません。その場合は、Try-Parseパターンが使えます。

Try-Parseパターンは、メソッドとしては例外をthrowせず、boolで結果を返す方法です。

void Main()
{
    string path = @"filepath";

    // 戻り値としてはboolが返ってくるので、結果はout引数で受け取る。
    var succeeded = TryGetFileData(path, out var data);
    if (succeeded)
    {
        Console.WriteLine($"Suceeded to get data from {path}");
    }
}

bool TryGetFileData(string path, out byte[] fileBytes)
{
    try
    {
        fileBytes = File.ReadAllBytes(path);
        return true;

    }
    catch (IOException ex)
    {
        Console.WriteLine($"Failed to get file data. Error : {ex.Message} ");
        // throwはせず、戻り値でfalseを返し処理の失敗を知らせる。
        fileBytes = null;
        return false;
    }
}

"TryGetFileData()"メソッドでは、処理が成功したかどうかを、例外を投げて知らせる代わりに、戻り値としてfalseを返して知らせています。
また、本来なら戻り値として受け取っていた結果は、out引数として受け取っています。(処理失敗時は、null or 適当な失敗を表す値を代入。)

上述したように、Try-Parseパターンには、例外を投げるのと比較して、処理失敗時のコストが低いというメリットがあります。ただし、Try-Parseパターンには、メソッド呼び出し側に例外処理を強制できないというデメリットもあります。それを理解した上で、例外を投げるのか、Try-Parseパターンを使うかどうか判断しましょう。

Try-Parseパターンにはメリット・デメリットがある。場合によって例外を投げるのと使い分けるとよい。

  • Try-Parseパターンのメリット:処理失敗時のコストが例外を投げるより低い
  • Try-Parseパターンのデメリット:メソッド呼び出し側に例外処理を強制できない

Try-Parseパターンは、.NETのクラスだと、以下でも使われていますね。
このパターンの場合は、Try~という名前のメソッドにするのが慣習ですね。

  • System.Int32のTryparseメソッド

非同期メソッドの場合

非同期メソッドの場合、out引数が使えません。
その場合は、タプルで結果を返すなどして工夫しましょう。

async Task<(bool, string)> TryGetMessageAsync(Uri uri) { //(省略)}

全例外クラス継承元のExceptionクラスをcatchする場合

catch句では、対象となるException派生クラスを指定できる一方で、以下のように全ての例外クラスの継承元となるExceptionクラスも指定できてしまいます。(強制的に全ての例外をcatchすることができてしまう。)
これは場合によっては、適切な処理が行われず例外の発生が見えにくくなり、不具合の原因特定を難しくすることにつながってしまいます。

void CatchAllException()
{
    try
    {
        // 何かしらのエラーが起きるメソッド。
        RaiseError();
    }
    // 全ての例外をcatchする。
    // 復帰処理などを行う必要がある例外が発生した場合、後から原因が特定しにくいバグとなり得る。
    catch (Exception ex)  
    {
        Console.WriteLine($"Error : {ex.Message}");
    }
}

なので方針としては、派生大元のExceptionは基本的にそのままcatchしない。catchする場合は特定の場合のみとします。
では、catchしてよいのはどのような場合かを見ていきます。

派生大元のExceptionクラスをcatchしてよいとき

  1. 再度throwするとき。(catchの目的はログ出力のためなど。再throwの仕方に注意)
  2. 別の例外に変換するとき。
  3. 独自のエラーハンドリングをし、exceptionは再throwしないとき。(きちんとエラーハンドリングがされることが条件)

それぞれコード例を見てみます。

// ログを出力のためにcatchし、再throwする。
void Rethrow()
{
    try
    {
        RaiseError();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Failed to {nameof(Rethrow)}. : {ex.Message}");
        // throw exとは書いてはいけない。StackTraceが消えるため。
        throw;
    }
}

// 別の例外に変換する
void Convert()
{
    try
    {
        RaiseError();
    }
    catch (Exception ex)
    {
        // catchしたExceptionはInnerExceptionにする。
        throw new OriginalException($"Original exception occurred", ex);
    }
}

// 独自のエラーハンドリングをする。
void ErrorHandleSelf()
{
    try
    {
        RaiseError();
    }
    catch (Exception ex)
    {
        // HandleErrorメソッドできちんと処理が行われることが前提。極力使わない。
        HandleError(ex);
    }
}

1 再度throwするとき。

ログ出力などの任意の処理を行った後、再度throwします。このとき、"throw ex"と書くとStackTraceが消えてしまうため、"throw"とだけ書きます。

2 別の例外に変換するとき。

そもそもExceptionをそのままcatchすることは例外が握りつぶされてしまうことが一番の問題なので、別の例外に変換するのはOKです。ただし、変換前の例外情報も貴重なため、InnerExceptionとして残しておきます。
このパターン使われるのは以下の場合が多いかなと思います。

  • より適切な種類の例外に置き換えれるとき(より具体的な例外の方が不具合の原因を特定しやすい)
  • ライブラリ的に扱われるレイヤーで、そのライブラリから出される例外を統一したいとき

3. 独自のエラーハンドリングをし、exceptionは再throwしないとき。

多くの場合は全ての例外をカバーすることは難しいため、このやり方は推奨ではないです。
ただし、開発プロジェクト内でエラーハンドリングのやり方が統一されている場合などには使われることがあるかもしれません。

独自の例外クラスについて

C#では、Exceptionクラスを継承することによって、独自の例外クラスを作成できます。

class OriginalException : Exception
{
    public OriginalException() { /*(実装省略)*/ }
    public OriginalException(string? paramName) { /*(実装省略)*/ }
    public OriginalException(string? message, Exception? innerException) { /*(実装省略)*/ }
    public OriginalException(string? paramName, string? message) { /*(実装省略)*/ }
    protected OriginalException(SerializationInfo info, StreamingContext context) { /*(実装省略)*/ }
}  

独自例外クラスを作成するべきタイミングについては、Microsoftの公式ドキュメント-ユーザー定義の例外を作成する方法に書いているものそのままです。

.NET では、基底クラス Exception から最終的に派生した例外クラスの階層構造を提供します。 ただし、定義済みの例外のいずれも要件を満たさない場合は、Exception クラスから派生することによって、独自の例外クラスを作成できます。

つまり、独自の例外クラスは、積極的につくるものではなく、.NET標準で用意されてる例外クラスに該当するものがない場合のみ作成すると覚えておけば大丈夫です。
その他、独自例外を定義した方が、明らかに利用者側の復帰処理などに役立つ場合、あるいは一塊のコード内(ライブラリなど)で一貫した例外を使用したい場合などは独自例外をつくることを検討しましょう。

ちなみにですが、C#でJsonを簡単に取り扱えるライブラリJson.NETでは、"JsonException"という独自例外クラスが定義されており、ライブラリ利用側には"JsonException"(実際にはいくつかあるその派生クラス)が渡されるようになっています。独自例外クラスの使われ方について実際のコードが見たいかたは、参照してみると良いと思います。(JsonExceptionクラス)

また、独自例外作る際は、作法として定義しておくべきメソッドがいくつかあります。ドキュメントを参考にして定義しましょう。

XMLドキュメントコメント

C#では、メソッドやクラスなどに対してXML形式でドキュメントコメントを書けます。
メソッドのコメントでは、発生する可能性のある例外を示すためのタグ"<exception>"が用意されています。
メソッド利用側の実装の助けになるので積極的に書きましょう。

/// <summary>
/// Show <paramref name="arg"/> on standard output.
/// </summary>
/// <exception cref="ArgumentException">Throw if <paramref name="arg"/> is null or whitespace.</exception>
void Print(string arg)
{
    if (string.IsNullOrWhiteSpace(arg))
    {
        throw new ArgumentException($"{nameof(arg)} is null or whitespace. Please substitute proper value.");
    }

    Console.WriteLine($"Output : {arg}");
}

メソッド固有のルールではなく、例外処理を使う理由

Try-Parseパターンのように、例外処理構文を使わなくてもメソッド処理の成功・失敗を伝える方法はあります。
ただしその場合には、メソッド固有のルール(例えばTry-Parseパターンでは、成功・失敗を戻り値とするいうルール)を決めていることによるデメリットがあります。
この点についても、++C++;-例外の使い方で簡潔にまとめられているので引用させていだきます。(原文では、文書で書かれているものを私の言葉で少し書き直し、表形式にしています。)

メソッド固有のルールを使うデメリット 例外処理を使うメリット
例外の検出が面倒。
(呼び出すメソッド固有のルールを知っておく必要がある。)
正常動作部と例外処理部の区別が明確。
(throw, try-catch文に処理の仕方が統一されている。)
正常動作部と例外処理部の区別が分かりにくい。 正常動作部と例外処理部の区別が分かりやすい。
try の中には動作が正常な時の処理が、 catch の中には例外発生時の対処のみが書かれる。
メソッド利用者に例外処理を行うことを強制出来ない。 メソッド利用側に例外処理させることを強制できる。
(例外発生時に処理が行われないと、アプリが終了する。)
メソッドを呼び出すたびに例外処理用のコードを書く必要がある。 例外処理部(catch 節)や、正常・例外問わず必ず実行する必要がある部分(finaly 節)が一か所にまとまる。

このように、例外処理構文を使うことにより、メソッド固有のルールによるデメリットを大きくカバーすることができます。
なので実装の際は、まずは例外処理構文を使うことを検討、何か特別な理由があるならメソッド固有のルールを使うという方針でよいでしょう。

業務エラーには例外処理を使ってはいけない?

例外処理の利用方針パターン①で、利用法上の例外について説明しました。
ただ例外の利用方法について調べていると、「業務エラー(≒利用方上の例外)には例外処理を用いてはいけない」という記述をよく見かけます。

個人的には「業務エラーでもthrowはしてよい、ただしcatchはしない」という方針で良いと思います。
というのも、前節で書いた、throwを使わない場合に発生する「メソッド呼び出し側に例外処理を強制できない」といデメリットを避けるためです。

OSSではどうなってるか

Json.NETではどのように例外処理が使われているか軽く調べてみました。
以下は、ソリューション内のNewtonsoft.Jsonプロジェクトで使われいてる、throw, catchの大体の数になります。(grepでコード内のコメントも拾ってしまっているので、正確な数ではないです。)

:~/Json.NET/Src/Newtonsoft.Json$ grep -r -n "throw new" | wc -l    371
:~/Json.NET/Src/Newtonsoft.Json$ grep -r -n "catch" | wc -l   51

見てわかるように、新しい例外をthrowしている数に対してcatchしている数が少ないです。
実際のコードを見てみても、概ね「業務エラーでもthrowはしてよい、ただしcatchはしない」という方針で書かれているのかなぁという印象を受けました。

おわりに

例外処理を利用方針について、自分の勉強用に調べた内容をまとめました。
同じようなことを知りたがっている人のお役に立てれば幸いです。

もし内容の間違いや改善点などあれば、ご指摘いただけると嬉しいです。

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

Chrome拡張機能を作ってみよう②

前回はChrome拡張機能からファイルのダウンロードまでを投稿しました。
今回はその続きです。

Chrome拡張機能とローカルプログラムの連携

Chrome拡張機能とローカルプログラムとの連携について
今回はローカルプログラムをvisual studioで作ったC#のプログラムとします。

下準備

まずは連携するための下準備をします。

最初に連携するプログラムの配置場所を決めます。
今回は
c:\extensionSample\program
の配下に置くことにします。

連携用JSONファイルを準備
場所に決まりはないですが連携用のjsonファイルをprogramフォルダの配下に配置する

program.josn
{
  "name": "put.message",
  "description": "特定のURLでメッセージを表示する。",
  "path": "C:\\extensionSample\\program\\program.exe",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://gfoihpmphbkdehkldlglbbcgkcpdfmam/"
  ]
}

nameは連携に必要です。今回は「put.message」としています。

pathにはプログラムのパスを指定します。

allowed_originsは拡張機能のIDを指定します。
chromeのアドレスバーに「chrome://extensions」と入力して
前回追加した拡張機能のIDを記載します。

レジストリに登録
次に、レジストリにjsonファイルを登録します。
コンピューター\HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts
にjsonファイルのnameに指定した「put.message」キーを作成する。
既定のデータにjsonファイルパスを指定する。
今回は「c:\extensionSample\program\program.josn」とします。
image.png

C#プログラム

では実際にC#のプログラムです。
(結構適当に書いています。ご容赦を。。。)

using Microsoft.JScript;
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

namespace program
{
    /// <summary>
    /// ローカルプログラム
    /// </summary>
    class localprogram
    {
        private static bool NativeMessageFlg = true;
        private static readonly Encoding enc = Encoding.UTF8;

        /// <summary>
        /// メイン処理
        /// </summary>
        static void Main(string[] args)
        {
#if DEBUG
            // デバッグ時のみ、プロセスにアタッチ
            System.Diagnostics.Debugger.Launch();
#endif
            try
            {
                // 起動処理の確認(Chrome拡張機能 or その他(直接起動))
                if (args.Length > 0 && args[0].IndexOf("chrome-extension") >= 0)
                {
                    // Chrome拡張機能
                    NativeMessageFlg = true;
                }
                else
                {
                    // その他(直接起動)
                    NativeMessageFlg = false;
                }

                Request request = new Request();

                if (NativeMessageFlg)
                {
                    // Chrome拡張機能から取得
                    Stream stdin = Console.OpenStandardInput();
                    byte[] bytes = new byte[4];
                    stdin.Read(bytes, 0, 4);

                    int length = BitConverter.ToInt32(bytes, 0);

                    string input = "";
                    for (int i = 0; i < length; i++)
                    {
                        input += (char)stdin.ReadByte();
                    }
                    stdin.Close();
                    using (var ms = new MemoryStream(enc.GetBytes(input)))
                    {
                        var jsonSer = new DataContractJsonSerializer(typeof(Request));
                        request = (Request)jsonSer.ReadObject(ms);
                    }
                }
                // Chrome拡張機能から受け取った「Action」によって処理を分ける
                Response response = new Response();
                int limit = 1024 * 1024 - 2;
                string stringText = string.Empty;
                switch (request.Action)
                {
                    case "putmessage":
                        string responsJson;
                        response.Code = 0;
                        response.Data = request.Data;
                        responsJson = ConvertJson(response, typeof(Response));
                        string stringText1 = GlobalObject.encodeURIComponent(responsJson);
                        while (stringText1.Length >= limit)
                        {
                            WriteString("\"" + stringText1.Substring(0, limit) + "\"");
                            stringText1 = stringText1.Substring(limit);
                        }
                        WriteString("\"" + stringText1 + "\"");
                        break;
                    default:
                        response.Code = 9;
                        response.Data = "エラー";
                        responsJson = ConvertJson(response, typeof(Response));
                        string stringTextErr = GlobalObject.encodeURIComponent(responsJson);
                        while (stringTextErr.Length >= limit)
                        {
                            WriteString("\"" + stringTextErr.Substring(0, limit) + "\"");
                            stringTextErr = stringTextErr.Substring(limit);
                        }
                        WriteString("\"" + stringTextErr + "\"");
                        break;
                }
            }
            catch (Exception ex)
            {
            }

            Environment.Exit(0);
        }

        /// <summary>
        /// オブジェクト→JSONへの変換
        /// </summary>
        private static string ConvertJson(object obj, Type type)
        {
            using (var ms = new MemoryStream())
            {
                var serializer = new DataContractJsonSerializer(type);
                serializer.WriteObject(ms, obj);
                return enc.GetString(ms.ToArray());
            }
        }

        private static void WriteString(string stringData)
        {
            byte[] bytes = BitConverter.GetBytes(stringData.Length);
            Stream stdout = Console.OpenStandardOutput();
            for (int i = 0; i < 4; i++)
            {
                stdout.WriteByte(bytes[i]);
            }
            byte[] b = Encoding.UTF8.GetBytes(stringData);
            stdout.Write(b, 0, b.Length);
            stdout.Flush();
            stdout.Close();
        }

        /// <summary>
        /// リクエストパラメータ
        /// </summary>
        [DataContract]
        struct Request
        {
            [DataMember(Name = "Action")]
            public string Action { get; set; }
            [DataMember(Name = "Data")]
            public string Data { get; set; }
        }

        /// <summary>
        /// レスポンス
        /// </summary>
        [DataContract]
        struct Response
        {
            [DataMember(Name = "Code")]
            public int Code { get; set; }
            [DataMember(Name = "Data")]
            public string Data { get; set; }
        }
    }
}

特にこれと言って注意点はないと思います。

System.Diagnostics.Debugger.Launch();
の箇所はデバックするときに便利なので付けています。

ビルドしたらprogram.exeファイルを
C:\extensionSample\program\に配置します。

次に「background.js」を修正します。

background.js
/*
  初期起動時の処理
*/
// インストール時かバージョンアップ時
chrome.runtime.onInstalled.addListener(function() {
  initialize();
});

// ブラウザ起動時
chrome.runtime.onStartup.addListener(function() {
  initialize();
});


function initialize() {
  // ファイルダウンロード先
  var dlFileName = "http://localhost/sample.txt";
  // ファイルを取得
  $.ajax({
    url: dlFileName,
    type: "GET",
    dataType: 'binary',
    responseType:'arraybuffer',
    timeout: 500
  })
  // 成功時
  .done(errorHandle(function (response) {
    var data = response;
    // ArrayBufferで取得するので
    var textfiledata = String.fromCharCode(...new Uint8Array(data));
    console.log(textfiledata);
    // 渡す前にbase64エンコードしておく
    textfiledata = btoa(textfiledata); 
    // ダウンロードした内容をprogramに連携する
    chrome.runtime.sendNativeMessage('put.message',
    {Action: "putmessage", Data: textfiledata},
    errorHandle(function(response, thread){
      // デコードしてJSON形式にする
      var getData = JSON.parse(decodeURIComponent(response));
      // 受け取ったコードがエラーの場合
      if(getData.Code != 0){
        throw new Error('programでエラーが発生');
      }
      // 受け取ったデータをlocalStorageに保存しておく
      localStorage.setItem('urllist', atob(getData.Data));
      return true;
    }));
    // 成功した場合は拡張機能のアイコンを切り替える
    chrome.browserAction.setIcon({path:"images/success.png"});
    // localStorageにステータスを保存
    localStorage.setItem('Status', 'ok');
  }))
  // 失敗時
  .fail(errorHandle(function () {
    // localStorageにステータスを保存
    localStorage.setItem('Status', 'ng');
  }));
  return true;
}
/**
 * 例外をまとめて処理する
 */
function errorHandle(process) {
  return function(){
    try {
      return process.apply(this, arguments);
    } catch (e) {
      chrome.browserAction.setIcon({path:"images/abnormal.png"});
      console.error(e);
    }
  };
}

chrome.runtime.sendNativeMessage([program.josnで指定したname],渡す値)
がローカルプログラム呼び出しの部分です。
データのやり取りはjson形式で行います。

動かしてみる

Chromeブラウザのアドレスバーに「chrome://extensions」と入力し
拡張機能を更新する。

バックグランドページを開いて、ApplicationのLocal Storageから対象の拡張機能IDを選択する。
urllistにsample.txtの内容が入っていれば成功です。


今回はここまで
次回はURL監視について書いていきます。

(urlエンコードはいらなかったかな)

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

Chrome拡張機能を作ってみよう①

背景

Chromeの拡張機能を作ってみたが、どこかにアウトプットしないと絶対に忘れると思ったため
基本的に自分用メモなのでよくわからなかったらすみません。

作るもの

① Chrome拡張機能からWeb上のファイルをダウンロードする。
② ダウンロードしたファイルをC#で作ったexeに連携する。
③ exeでファイル内容を受け取りChrome拡張機能に返す。
④ exeから受け取ったURLと同じURLを開いた場合にメッセージを表示する。
補足)Chrome拡張機能をChromeウェブストアを介さずにインストールさせる

準備するもの

・テキストエディタ
・visual studio
・xampp(ファイルダウンロード用)

Chrome拡張機能からファイルのダウンロード

今回はc:\extensionSample\extensions
というフォルダを作って、この中に作っていきます。

extensionsの中はこんな感じです
[css]
  popup.css - popup.htmlのスタイル
[images]
  abnormal.png - Chrome拡張機能エラー時のアイコン
  success.png - Chrome拡張機能正常時のアイコン
  icon16.png
  icon48.png
  icon128png
[js]
  background.js - 動作するファイル
  jquery-3.4.1.js
  jquery.binarytransport.js
  popup.js - popup.htmlにて動作する
  message.js - メッセージを表示するファイル
manifest.json - 設定ファイル
popup.html - Chrome拡張機能をクリックした際に表示する画面

この構成で作っていきます。

まず、manifest.json_から

manifest.json
{
  "manifest_version": 2,
  "name": "message表示",
  "version": "1.0",
  "description" : "Chrome拡張機能のサンプル",
  "permissions": [
    "nativeMessaging"
  ],
  "icons": {
    "16": "images/icon16.png",
    "48": "images/icon48.png",
    "128": "images/icon128.png"
  },
  "browser_action" : {
    "default_icon" : "images/abnormal.png",
    "default_title" : "Chrome拡張機能のサンプル",
    "default_popup": "popup.html"
  },
  "background": {
    "scripts": [
      "js/jquery-3.4.1.js",
      "js/jquery.binarytransport.js",
      "js/background.js"
    ],
    "persistent": false
  },
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": [
        "js/jquery-3.4.1.js",
        "js/jquery.binarytransport.js",
        "js/message.js"
      ]
    }
  ]
}

permissionsにはコンピューターにインストールされたアプリケーションと拡張機能との間のメッセージ交換をするためnativeMessagingを指定します。

URLを監視するのでmatchesはall_urlsを指定します。

次にbackground.js

background.js
/*
  初期起動時の処理
*/
// インストール時かバージョンアップ時
chrome.runtime.onInstalled.addListener(function() {
  initialize();
});

// ブラウザ起動時
chrome.runtime.onStartup.addListener(function() {
  initialize();
});


function initialize() {
  var dlFileName = "http://localhost/sample.txt";
  $.ajax({
    url: dlFileName,
    type: "GET",
    dataType: 'binary',
    responseType:'arraybuffer',
    timeout: 500
  })
  // 成功時
  .done(errorHandle(function (response) {
    var data = response;
    // ArrayBufferで取得するので
    var textfiledata = String.fromCharCode(...new Uint8Array(data));
    console.log(textfiledata);
    // 成功した場合は拡張機能のアイコンを切り替える
    chrome.browserAction.setIcon({path:"images/success.png"});
    // localStorageにステータスを保存
    localStorage.setItem('Status', 'ok');
  }))
  // 失敗時
  .fail(errorHandle(function () {
    // localStorageにステータスを保存
    localStorage.setItem('Status', 'ng');
  }));
  return true;
}
/**
 * 例外をまとめて処理する
 */
function errorHandle(process) {
  return function(){
    try {
      return process.apply(this, arguments);
    } catch (e) {
      chrome.browserAction.setIcon({path:"images/abnormal.png"});
      console.error(e);
    }
  };
}

あとはpopup系です

popup.js
$(function() {
  /*** localStorageからステータスを取得。 ***/
  var Status = localStorage.getItem('Status');
  if(Status == 'ok'){
    $('#status').html('<b>正常</b>');
  }else{
    $('#status').html('<b>異常</b>');
  }
});
popup.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-cache">

    <link href="../css/popup.css" rel="stylesheet">

    <script src="../js/jquery-3.4.1.js" ></script>
    <script src="../js/popup.js" ></script>
  </head>

  <body id="body">
  <div class="box">
    <div class="header">
      <h3 style="margin: 5px 0">動作状況</h3>
    </div>
    <div class="content">
      <p id="status"></p>
    </div>
  </div>

  </body>
</html>
popup.css
body {
  font-family: sans-serif;
  width: 300px;
  height: 180px;
  background-color: lightgray;
}
p {
  margin: 0px;
}
.box {
  border: solid 1px;
  width: 95%;
  height: 90%;
  padding: 5px;
}

message.jsはとりあえず空でファイルだけ作っておきます。

動かしてみよう

ここまで出来たら動かしてみよう

Chromeのアドレスバーに
chrome://extensionsを入力して拡張機能を表示する

右上の「デベロッパー モード」をONにして「パッケージ化されていない拡張機能を読み込む」から
c:\extensionSample\extensionsを選択する。

一覧に出てきたら、右上の「拡張機能」から確認してみます。

http://localhost/sample.txt
が存在すれば正常表示、無ければ異常表示になると思います。

sample.txtの内容は以下のようにしています。
{"1" : "http://localhost/ap1/","2" : "http://localhost/ap2/"}


今回はここまで
次回はローカルプログラムとの連携について書こうかと思います。

(確認してないけどerrorHandleはちゃんと動いているかな・・・)

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

C#でAtCoder Beginners Selection(ABC088B - Card Game for Two)

準備

C#でAtCoderデビューのための準備
のあとで AtCoder Beginners Selection をやってみました。

問題文

ABC088B - Card Game for Two
https://atcoder.jp/contests/abs/tasks/abc088_b

提出結果

using System;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        int n = int.Parse(Console.ReadLine());
        int[] a = Console.ReadLine().Split().Select(int.Parse).ToArray();
        Array.Sort(a);
        Array.Reverse(a);
        int Alice = 0;
        int Bob = 0;
        for (int i = 0; i < n; i++)
        {
            if (i % 2 == 0)
            {
                Alice += a[i];
            }
            else
            {
                Bob += a[i];
            }
        }
        int tensa = Alice - Bob;
        Console.WriteLine($"{tensa}");
    }
}

テスト実行

image.png
image.png
image.png

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

C#でAtCoder Beginners Selection(ABC083B - Some Sums)

準備

C#でAtCoderデビューのための準備
のあとで AtCoder Beginners Selection をやってみました。

問題文

ABC083B - Some Sums
https://atcoder.jp/contests/abs/tasks/abc083_b

提出結果

using System;

class Program
{
    static void Main(string[] args)
    {
        string[] input = Console.ReadLine().Split(' ');
        int n = int.Parse(input[0]);
        int a = int.Parse(input[1]);
        int b = int.Parse(input[2]);

        int souwa = 0;
        for (int i = 1; i <= n; i++)
        {
            // 強引だけれど結果オーライ
            int i10000 = i / 10000;
            int i1000 = (i % 10000) / 1000;
            int i100 = (i % 1000) / 100;
            int i10 = (i % 100) / 10;
            int i1 = i % 10;
            int wa = i10000 + i1000 + i100 + i10 + i1;
            if ((wa >= a) && (wa <= b))
            {
                souwa += i;
            }
        }
        Console.WriteLine($"{souwa}");
    }
}

テスト実行

image.png
image.png
image.png

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

intarfaceについて

intarfaceとは

 具体的な処理の内容は書かず、クラスが実装すべき規約(そのメソッドで何を呼び出すことが出来るのか)を定めるもの。つまり、クラスでどんなメソッドを実装する必要があるかという構造だけを定めた、論理的な存在。

存在価値は?

・「このインターフェースを使うときはこのメソッドの中身を必ず作ってね!」とか、「そのメソッドには、必ずこんな引数を渡してね!」などとメソッドの実装を強制できる。
・複数のクラスが同じintarfaseについて、それぞれ異なる処理内容を実装することが出来る。(ポリモーフィズム)

使用例

intarfaceの定義例

public interface ISeriees {
    int GetNext();
      void Reset();
      void SetStart(int x);
}

見てわかる通り、どのメソッドにも処理は実装されていない。その代わりに、intarfaseを実装するクラスでは、そのintarfaseに含まれるすべてのメソッドを実装する必要がある。

intarfaceを実装するクラスの例

class ByTwos : ISeries {
    int start;
    int val;

    public ByTwos()
    {
        start = 0;
        val = 0;
    }

    public int GetNext()
    {
        val += 2;
        return  val;
    }

    public void Reset()
    {
        start = 0;
        val = 0;
    }

    public void SetStart(int x)
    {
        start = x;
        val = x;
    }
}

定義したintarfaceを1つ以上のクラスに実装することができる。ByTwosクラスが3つすべてのメソッドを実装していることからも分かる通り、intarfaceで定義したすべてのメンバーを実装する必要がある。いくつかのメソッドだけを使うといったことはできない。これは使う人それぞれがバラバラな使い方をしてしまうのを防ぐため。

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

インターフェースについて

インターフェースとは

 具体的な処理の内容は書かず、クラスが実装すべき規約(そのメソッドで何を呼び出すことが出来るのか)を定めるもの。つまり、クラスでどんなメソッドを実装する必要があるかという構造だけを定めた、論理的な存在。

存在価値は?

・「このインターフェースを使うときはこのメソッドの中身を必ず作ってね!」とか、「そのメソッドには、必ずこんな引数を渡してね!」などとメソッドの実装を強制できる。
・複数のクラスが同じインターフェースについて、それぞれ異なる処理内容を実装することが出来る。(ポリモーフィズム)

使用例

interfaceの定義例

public interface ISeriees {
    int GetNext();
      void Reset();
      void SetStart(int x);
}

見てわかる通り、どのメソッドにも処理は実装されていない。その代わりに、インターフェースを実装するクラスでは、そのインターフェースに含まれるすべてのメソッドを実装する必要がある。

interfaceを実装するクラスの例

class ByTwos : ISeries {
    int start;
    int val;

    public ByTwos()
    {
        start = 0;
        val = 0;
    }

    public int GetNext()
    {
        val += 2;
        return  val;
    }

    public void Reset()
    {
        start = 0;
        val = 0;
    }

    public void SetStart(int x)
    {
        start = x;
        val = x;
    }
}

定義したインターフェースを1つ以上のクラスに実装することができる。ByTwosクラスが3つすべてのメソッドを実装していることからも分かる通り、インターフェースで定義したすべてのメンバーを実装する必要がある。いくつかのメソッドだけを使うといったことはできない。これは使う人それぞれがバラバラな使い方をしてしまうのを防ぐため。

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