技術とか戦略とか

証券レガシーシステムを8年いじってから転職した普通の文系SEによるブログ。技術のみではなく趣味の戦略考察についても。

C#:デリゲートの説明(Action・Func、マルチキャストデリゲート含む)

C#のデリゲートの解説は既に色々と出回っていますが、わかりにくい概念であり、色々な人が異なる切り口で説明した方が良いと思うので、私の方でも記事にします。
 
----------------------------------------
 
デリゲートとは、一言で言えば「関数を変数として扱う」機能です。
個人的には、「1つの関数のみを定義したクラスやインターフェースのようなもの」と捉えた方が分かりやすいと思います。
 
実務ではコールバック(本処理の終わりに特定の終了処理をさせる)をさせたい場合に使用されることが多いようです。
 
以下はサンプルコードです。
理解を助けるためにコメントを入れているので、それもご参照ください。
 
【サンプルコード1】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateTest
{
    class Program
    {
        // delegateの型「EndFunc」を定義
        public delegate string EndFunc(string s);

        static void Main(string[ ] args)
        {
            // 後述のメソッド「MakeEndMessage」を、
            // 「EndFunc」型の変数「endFunc」に格納
            EndFunc endFunc = MakeEndMessage;

            // クラスのオブジェクトと同じように、
            // 変数として引き渡すことができる。
            SomethingFunc(endFunc);
        }

        static void SomethingFunc(EndFunc endFunc)
        {
            Console.WriteLine("DoSomething");

            // 引き渡された関数を実行
            Console.WriteLine(endFunc("Something"));

            Console.ReadLine();
        }

        // 「EndFunc」型(引数string・戻り値string)の関数
        // 今回delegateする関数
        static string MakeEndMessage(string s)
        {
            return "End" + s;
        }

    }
}
 
【実行結果1】
DoSomething
EndSomething
----------------------------------------
 
引き渡す関数は匿名メソッドやラムダ式でも記述可能であり、これらを利用することで関数の定義を省略することができます。
 
また、引き渡された関数を受け取る際、.Netで用意されている以下のジェネリック定義を使用することができます。
ジェネリック定義を使用することで、デリゲートを省略することができます。
 
・System.Action型
 戻り値がない場合に使用
 Action、Action<引数1>、Action<引数1,引数2>…等
 
・System.Func型
 戻り値がある場合に使用
 Func<戻り値>、Func<引数1,戻り値>、Func<引数1,引数2,戻り値>…等
 
以下は、ラムダ式とSystem.Func型を使用し、サンプルコード1を簡略化する例です。
 
【サンプルコード2】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateTest
{
    class Program
    {

        static void Main(string[ ] args)
        {
            // こんな感じでラムダ式を用いて、
            // 関数の定義を省略しても良い。
            // (引数) => {処理}
            SomethingFunc( (string s) => {
                return "End" + s;
            });
        }

        // こんな感じでジェネリック定義を使用し、
        // delegateの定義を省略することもできる。
        static void SomethingFunc(Func<string,string> endFunc)
        {
            Console.WriteLine("DoSomething");

            // 引き渡された関数を実行
            Console.WriteLine(endFunc("Something"));

            Console.ReadLine();
        }

    }
}
 
【実行結果2】
DoSomething
EndSomething
----------------------------------------
 
引き渡す関数は、+演算子で追加したり-演算子で削除したりすることもできます。
マルチキャストデリゲート)
 
以下、+演算子や-演算子を試してみた例です。
 
【サンプルコード3】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateTest
{
    class Program
    {
        // delegateの型「EndFunc」を定義
        public delegate void EndFunc(string s);

        static void Main(string[ ] args)
        {
            EndFunc endFunc;

            // +演算子で実行する関数を追加
            endFunc  = MakeEndMessage;
            endFunc += MakeEndMessage2;
            Console.WriteLine("[Append Func]");
            SomethingFunc(endFunc);

            // -演算子で実行する関数を削除
            endFunc -= MakeEndMessage;
            Console.WriteLine("[Delete Func]");
            SomethingFunc(endFunc);

            Console.ReadLine();
        }

        static void SomethingFunc(EndFunc endFunc)
        {
            Console.WriteLine("DoSomething");

            // 引き渡された関数を実行
            endFunc("Something");
        }

        // 「EndFunc」型(引数string・戻り値無し)の関数
        static void MakeEndMessage(string s)
        {
            Console.WriteLine("End" + s);
        }

        // 「EndFunc」型(引数string・戻り値無し)の関数
        static void MakeEndMessage2(string s)
        {
            Console.WriteLine("End2" + s);
        }

    }
}
 
【実行結果3】
[Append Func]
DoSomething
EndSomething
End2Something
[Delete Func]
DoSomething
End2Something

Neutral Face Emoji Tools(Bulk Emoji Uploader)で一括アップロード画面が出てこない件

色々調べてみると、2018~2019年頃にSlack側で絵文字アップロード画面のインターフェースが変わったように見えます。
そのため、最新のツールを落とさないと一括アップロード画面が出てきません。
 
2020/04/04時点の最新は以下で、先ほど試して一括アップロードできました。

https://chrome.google.com/webstore/detail/neutral-face-emoji-tools/anchoacphlfbdomdlomnbbfhcmcdmjej

 
古いツールへのリンクを掲載している記事もあるので注意が必要です。
この記事の情報もいつ古くなるかわからないので、ご自身で最新のツールを探すことをお勧めします。
(ツール名でググるだけで調べられます)

C#:数値リテラル一覧

javaでは数値リテラルの扱いに注意が必要になることがあります。
C#の仕様も気になったので、一覧にまとめてみました。
f:id:akira2kun:20200404145529j:plain

 
javaとの違いは以下の通りです。
・整数型に符号無し(u)、実数型にdecimal型(m)も指定できる
 ※decimal型は丸め誤差が発生しない実数型、javaで言うBigDecimal
・整数型の場合、精度や符号有無を自動判定する
 
なお、以下のように、接尾辞・小数点無しの数値同士の計算結果を実数型の変数に格納する場合に、整数型として計算されてしまい小数点以下で切り捨てられてしまうのはjavaと同じなので、注意が必要です。
 
【サンプルコード】
・Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LiteralTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // 右辺の計算結果は整数型として扱われる
            double doubleValue1 = 1 / 2;
            Console.WriteLine("[1 / 2]");
            Console.WriteLine(doubleValue1);

            // 右辺の計算結果は実数型(double)として扱われる
            double doubleValue2 = 1.0 / 2.0;
            Console.WriteLine("[1.0 / 2.0]");
            Console.WriteLine(doubleValue2);

            // 右辺の計算結果は実数型(double)として扱われる
            double doubleValue3 = 1d / 2d;
            Console.WriteLine("[1d / 2d]");
            Console.WriteLine(doubleValue3);

            Console.ReadKey(true);

        }
    }
}
 
【実行結果】
[1 / 2]
0
[1.0 / 2.0]
0.5
[1d / 2d]
0.5

java:WEBからHTMLファイルを取得するサンプルプログラム

行きつけのサイトのログ(HTMLファイル)を定期的に取得しているのですが、その時に使用しているサンプルプログラムを公開します。
Javaの実行環境が整っているWindowsOSで、LogGet.java と LogGet.bat を同じディレクトリに置き、LogGet.bat を実行すると、「html」フォルダが出来上がりそこにHTMLファイルが格納されます。
取得するHTMLファイルのURL、及びHTMLファイルのローカルでのファイル名は、LogGet.java を修正して変更します。
HTMLファイルの文字コードは「EUC-JP」を前提としていますので、他の文字コードが使用されている場合は LogGet.java の「EUC-JP」の箇所を適宜修正してください。
 
【サンプルコード】
・LogGet.java
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.net.URL;
 
public class LogGet {
 
    public static void main(final String[ ] args){
        /*  利用者がいじる所はここです。
         *   ・"~.html"となっている箇所は取得ログのファイル名です。
         *    適宜書き換えてください。
         *     ※ファイル名が被った場合はログを取得しません
         *   ・取得する必要が無いログはコメントアウトしてください。
         */
        //LogGet("ログ1","http://hoge.html","ほげ.html");
          LogGet("ログ2","http://fuga.html","ふが.html");
    }
   
    public static void LogGet(String logName, String logUrl, String fileName){
        File f = new File("html\\" + fileName);
        if(f.exists()){
            System.out.println
                ("重複したファイル名 " + fileName + " が与えられたため " +
                 logName + " は取得しません。");
            return;
        }
        URL url = null;
        InputStream is = null;
        InputStreamReader isr = null;
        FileOutputStream fos = null;
        OutputStreamWriter osw = null;
        try {
            url = new URL(logUrl);
            is = url.openStream();
            isr = new InputStreamReader(is,"EUC-JP");
            fos = new FileOutputStream("html\\" + fileName);
            osw = new OutputStreamWriter(fos,"EUC-JP");
            while(true) {
                int i = isr.read();
                if (i == -1) {
                    break;
                }
                osw.write( (char)i);
            }
            System.out.println
                (logName + " をファイル名 " + fileName + " で取得しました。");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                isr.close();
                is.close();
                osw.close();
                fos.close();
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
}
 
・LogGet.bat
@echo off
rem JDKをインストールしていない場合はインストールしてください。
rem https://qiita.com/ko2a/items/69fa8a5366d7449500ca

rem 都度コンパイルを実施(javaスクリプト言語っぽく利用)
if exist .\LogGet.class (del .\LogGet.class)
javac -encoding UTF-8 .\LogGet.java

rem 処理実行
if not exist html\ (mkdir html)
java -classpath . LogGet

rem 処理終了
del .\LogGet.class
pause
exit

クラス図とjavaソースの対応一覧

クラス図とはUMLの一種で、以下のような形でクラスの定義と各クラスの関係を表す設計図のことです。
f:id:akira2kun:20200329020031j:plain

 
今回は、クラス図で使われる記法とjavaソースの対応について、一覧にしてみました。
参考になれば幸いです。
 
【クラス定義】
f:id:akira2kun:20200329020054j:plain

 
【各クラスの関係】

f:id:akira2kun:20200329020121j:plain

javaでのCommandパターン

Commandパターンとはデザインパターンの一種で、1つ1つのコマンド(命令)をそのままオブジェクトとして表現するパターンです。
コマンドをオブジェクトとして表現することで、コマンドの管理(追加・削除・実行)が可能になるというメリットがあります。
 
今回は、ファイルやフォルダを作成するコマンドを管理するサンプルコードを作成してみました。
Commandクラスが1つ1つのコマンドを表し、コマンドの管理はInvokerクラスで行い、コマンドの実際の処理はReceiverクラスで記述します。
 
【サンプルコード】
・Command.java
// コマンドの抽象クラス
public abstract class Command {

    protected String nameOfFileFolder;
    protected Receiver receiver;

    // コンストラクタ:生成するファイル名・フォルダ名をセット
    public Command(String nameOfFileFolder) {
        this.nameOfFileFolder = nameOfFileFolder;
    }

    // コマンドの受け手をセット
    public void setReceiver(Receiver receiver) {
        this.receiver = receiver;
    }

    // コマンドの受け手に処理を依頼
    public abstract void execute();

}
 
・ConcreteCommandForOperation.java
// 運用で使用するフォルダ・ファイル作成を指示するクラス
public class ConcreteCommandForOperation extends Command {

    public ConcreteCommandForOperation(String nameOfFileFolder) {
        super(nameOfFileFolder);
    }

    public void execute() {
        receiver.action
            ("C:\\pleiades\\workspace\\Hello\\conf\\" + nameOfFileFolder);
    }

}
 
・ConcreteCommandForTemporary.java
// 一時フォルダ・ファイル作成を指示するクラス
public class ConcreteCommandForTemporary extends Command {

    public ConcreteCommandForTemporary(String nameOfFileFolder) {
        super(nameOfFileFolder);
    }

    public void execute() {
        receiver.action
            ("C:\\tmp\\" + nameOfFileFolder);
    }

}
 
・Receiver.java
// コマンドの受け手の抽象クラス
public interface Receiver {

    // 処理実行
    public abstract void action(String path);

}
 
・ConcreteReceiverMkfil.java
import java.io.File;
import java.io.IOException;
// ファイル作成を行うReceiver
public class ConcreteReceiverMkfil implements Receiver {

    public void action(String path) {
        File file = new File(path + ".txt");
        try {
            if (file.createNewFile()){
            }else{
                System.out.println(path + "作成に失敗しました。");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
 
・ConcreteReceiverMkdir.java
import java.io.File;
// フォルダ作成を行うReceiver
public class ConcreteReceiverMkdir implements Receiver {

    public void action(String path) {
        File file = new File(path);
        if (file.mkdir()){
        }else{
            System.out.println(path + "作成に失敗しました。");
        }
    }

}
 
・Invoker.java
import java.util.Iterator;
import java.util.Stack;
// コマンドの追加・取消・実行を管理するクラス
public class Invoker {

    private Stack<Command> commandStack = new Stack<Command>();

    // コマンドを追加する
    public void addCommnad(Command command) {
        commandStack.push(command);
        System.out.println
            (commandStack.size() + "個目のコマンドを登録しました。");
    }

    // 直近のコマンドを削除する
    public void undoCommnad() {
        if(commandStack.empty()) {
            System.out.println("削除するコマンドがありません。");
        } else {
            commandStack.pop();
            System.out.println
                ( (commandStack.size()+1) + "個目のコマンドを削除しました。");
        }
    }

    // コマンドを順番に実行する
    public void execute() {
        System.out.println
            (commandStack.size() + "個のコマンドを実行します。");
        Iterator<Command> itetator = commandStack.iterator();
        while (itetator.hasNext()) {
            itetator.next().execute();
        }
        System.out.println("コマンドを実行しました。");
    }

}
 
・CommandMain.java
// メインクラス
public class CommandMain {

    public static void main(String[] args) {

        // 初期処理
        Receiver receiverMkfil = new ConcreteReceiverMkfil();
        Receiver receiverMkdir = new ConcreteReceiverMkdir();
        Invoker invoker = new Invoker();

        // コマンドの作成(5個)
        System.out.println("Aファイルを作成するコマンドを登録します。");
        Command commandA = new ConcreteCommandForOperation("A");
        commandA.setReceiver(receiverMkfil);
        invoker.addCommnad(commandA);
        System.out.println("Bフォルダを作成するコマンドを登録します。");
        Command commandB = new ConcreteCommandForOperation("B");
        commandB.setReceiver(receiverMkdir);
        invoker.addCommnad(commandB);
        System.out.println("C一時ファイルを作成するコマンドを登録します。");
        Command commandC = new ConcreteCommandForTemporary("C");
        commandC.setReceiver(receiverMkfil);
        invoker.addCommnad(commandC);
        System.out.println("D一時フォルダを作成するコマンドを登録します。");
        Command commandD = new ConcreteCommandForTemporary("D");
        commandD.setReceiver(receiverMkdir);
        invoker.addCommnad(commandD);
        System.out.println("Eファイルを作成するコマンドを登録します。");
        Command commandE = new ConcreteCommandForOperation("E");
        commandE.setReceiver(receiverMkfil);
        invoker.addCommnad(commandE);

        // 直近のコマンドの削除
        invoker.undoCommnad();

        // コマンドの実行
        invoker.execute();

    }

}
 
【実行結果】
・コンソール出力
Aファイルを作成するコマンドを登録します。
1個目のコマンドを登録しました。
Bフォルダを作成するコマンドを登録します。
2個目のコマンドを登録しました。
C一時ファイルを作成するコマンドを登録します。
3個目のコマンドを登録しました。
D一時フォルダを作成するコマンドを登録します。
4個目のコマンドを登録しました。
Eファイルを作成するコマンドを登録します。
5個目のコマンドを登録しました。
5個目のコマンドを削除しました。
4個のコマンドを実行します。
コマンドを実行しました。
 
・ファイルやフォルダの生成確認
C:\pleiades\workspace\Hello\conf>cd C:\pleiades\workspace\Hello\conf

C:\pleiades\workspace\Hello\conf>dir
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\pleiades\workspace\Hello\conf のディレクト

2020/02/08  10:02    <DIR>          .
2020/02/08  10:02    <DIR>          ..
2020/02/08  10:02                 0 A.txt
2020/02/08  10:02    <DIR>          B
               1 個のファイル                   0 バイト
               3 個のディレクトリ  19,379,642,368 バイトの空き領域

C:\pleiades\workspace\Hello\conf>
C:\pleiades\workspace\Hello\conf>cd C:\tmp\

C:\tmp>dir
 ドライブ C のボリューム ラベルは  です
 ボリューム シリアル番号は  です

 C:\tmp のディレクト

2020/02/08  10:02    <DIR>          .
2020/02/08  10:02    <DIR>          ..
2020/02/08  10:02                 0 C.txt
2020/02/08  10:02    <DIR>          D
               1 個のファイル                   0 バイト
               3 個のディレクトリ  19,379,642,368 バイトの空き領域

C:\tmp>

 

たまたま手元にあったもので立体マスクを作ってみた

無いなら作れば良い、ということで作ってみました。
原材料すら入手困難という話もありますが、補修布として使っている古着+使い捨てマスクから切り取ったゴムでも十分作成できました。
(ということで、特別に何か用意しなくても工夫次第で作成できると思います)
立体マスクなので平面マスクに比べて有効性が高いですし、洗って再利用もできるので経済的です。
 
f:id:akira2kun:20200328011621j:plain

 
こちらのページを参考に作成しました。
 
【無料型紙あり】20分で完成!立体マスクの作り方   nunocoto fabric.htm
https://book.nunocoto-fabric.com/15460
 
型紙を用意するように書かれていますが、PDFファイルを100%表示して大体のサイズ感を掴んでからフリーハンドで切り取る、でもある程度形を整えることはできます。少し大きめに切ってから後で細かく切って形を整える、縫い方を工夫して形を整える、といったことをすれば良いです。
(ちなみにアイロンや待ち針も使用しませんでした)
 
ゴムは適当に用意したもので長さが足りないので直接縫い付けてます。強度的には不安ですが、糸と針を持ち歩いていれば外出先でも縫い直せますし、どうしても駄目なら後で十分な長さのゴム(任意のゴムを結び合わせたものでも良い)を用意して通せば良いです。
(マスク関係無く糸と針は持ち歩いていた方が便利だと思います。シャツのボタンが外れた時の処置等でも使えるので。)
 
なお、参考にしたページでは20分で完成と書いてありますが、私が作った時は1時間~1時間半程度かかりました。
裁縫に慣れていたり2枚目以降を作成したりする場合は別かもしれませんが、そうでないならそれぐらいの時間は見た方が良いと思います。