技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

C#:イベントハンドラとは

イベントハンドラとはC#で標準で用意されている文法であり、イベント送受信の処理を記述するために用意されています。
 
クラスAでボタンのクリック等のイベントを発行する

クラスBはクラスAからイベントを受け取り業務処理を行う

クラスBは業務処理終了時にクラスAに何かしらの処理を返す
 
という動きを実現するための文法です。
 
文法的にはdelegateの応用であり、
public delegate void EventHandler(object sender, EventArgs e);
というdelegateが標準で用意されていると考えて問題ありません。
(自分で上記の定義を行っても同じ動きをします)
なお、"object sender"とは業務処理を行ったオブジェクト、"EventArgs e"とはイベント発行側のクラスに返すデータ、を指します。
 
上記の通り、イベントハンドラとは、引数に object sender と EventArgs e を持つdelegateです。
そのため、delegateを理解していれば、イベントハンドラの文法も理解できます。
自分で使うかどうかは別として、Visual StudioWindows Form を作成した時に自動生成されるソースコードや、他の人のソースコードを読む分には問題ないでしょう。
 
以下、サンプルコードです。
 
【サンプルコード】
・Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EventHandlerTest
{
    class Program
    {
        // イベント発生(exe実行)
        static void Main(string[] args)
        {
            EventReceiver eventReceiver = new EventReceiver();
            // イベントハンドラの登録
            eventReceiver.EventHandlerObj += new EventHandler(NoticeOfEnd);
            eventReceiver.Start();
            Console.ReadKey();
        }

        // イベント終了時に実行して欲しい処理
        static void NoticeOfEnd(object sender, EventArgs e)
        {
            // "object sender"は処理を行ったオブジェクト
            Console.WriteLine("処理実行オブジェクト:" + sender.ToString());

            // "EventArgs e"はイベント終了時に返されるデータ(今回は無し)
            if (e.Equals(EventArgs.Empty))
            {
                Console.WriteLine("返されるデータ:" + EventArgs.Empty);
            }

            // 終了通知
            Console.WriteLine("Notice Of End");
        }
    }
}
 
・EventReceiver.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EventHandlerTest
{
    class EventReceiver
    {
        // 下記の定義がされたdelegateが予め用意されている。
        // public delegate void EventHandler(object sender, EventArgs e);

        // データを持たないイベントデリゲートの宣言
        public event EventHandler EventHandlerObj;

        // 当クラスを継承することを考え protected virtual
        // (オーバーライド可能)
        protected virtual void EndProcedure(EventArgs e)
        {
            // 登録されたイベントハンドラがあれば、それを実行する。
            if (EventHandlerObj != null)
            {
                EventHandlerObj(this, e);
            }
        }

        // イベント受取時の処理
        public void Start()
        {
            Console.WriteLine("Business Logic");
            System.Threading.Thread.Sleep(3000);

            // 今回はEventArgsで返すデータは無し。
            // 返すデータがある場合はEventArgsを継承して定義し、
            // 継承したEventArgsを使うEventHandlerも独自に定義する。
            EndProcedure(EventArgs.Empty);
        }
    }
}
 
【実行結果】
Business Logic
処理実行オブジェクト:EventHandlerTest.EventReceiver
返されるデータ:System.EventArgs
Notice Of End

 

DLLから別のDLLを呼び出しているかも→「エラー:ファイルまたはアセンブリ 'hoge.dll'、またはその依存関係の1つが読み込めませんでした。指定されたモジュールが見つかりません。」

表題の通りです。
DLLから更に別のDLLを呼び出している場合があり、その「別のDLL」はエラーメッセージには出力されません。
そのため、必要だと思っているDLLを全て揃えているとしても、エラーメッセージに出力されている'hoge.dll'をexeと同じフォルダに置いているとしても、表題のエラーが出てしまう可能性があります。
 
どのDLLが呼び出されているのか、どのDLLが不足しているのかは、「Dependency Walker」というツールで調べることができます。
ダウンロードの方法や使い方は下記のページで紹介されています。
 
プログラムやDLLの依存関係を調べる「Dependency Walker」

https://webcli.jp/topics/dependencywalker/

 
Dependency Walker でDLLを取り込むと固まるように見えるかもしれませんが、それはそういうものです。
解析には10分ぐらいかかることもあるので、気長に待ちましょう。
 
解析結果はテキストファイルに出力することができます。
テキストファイルの下部には呼び出しているDLLの一覧があり、「指定されたファイルが見つかりません。」と出ているDLLが呼び出せていないDLLです。
ただし、呼び出せなくても問題のないDLLもあったりするので、表題のエラーが出ていないDLLや環境が別にあるなら、そのDLLや環境でも Dependency Walker でテキストファイルを出力し、結果を比べてみると良いでしょう。

C#:Mutexでの排他制御

2020/04/18 追記
実務でコピペするとAbandonedMutexExceptionが発生し得る若干行儀の悪いサンプルコードだったので、書き直しました。
 
-----------------------
 
排他制御の方法の一つとして、C#にはMutexと呼ばれる機能が用意されています。
何れか一つのスレッドがMutexによるロックを取得することができます。
他のスレッドによりロックが取得されている場合の処理を別途記述すれば、この機能を使用して排他制御が可能となります。
 
以下、サンプルコードです。
 
【サンプルコード】

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

namespace MutexTest
{
    class Program
    {
        static void Main(string[ ] args)
        {
            using (Mutex mutex = new Mutex(false)) // 使い終わったらスレッド解放
            {
                const int N = 3;
                // id0…0.7秒目~1.7秒目に掴む
                // id1…1.4秒目から待機、1.7~2.7秒目に掴む
                // id2…2.1秒目から待機、2.6秒目に諦める
                Parallel.For(0, N, id => // id0~2のスレッドを生成
                {
                    Thread.Sleep(id * 700);
                    // ロック取得、成功ならtrue
                    // 第一引数は待ち時間
                    if (mutex.WaitOne(500, false))
                    {
                        try
                        {
                            Console.WriteLine("ID" + id + "が掴みました。");
                            Thread.Sleep(1000);
                        }
                        finally
                        {
                            // Mutex資源解放
                            // 忘れてCloseすると待機待ち側でAbandonedMutexException
                            // Disposeでは実行されないためusingに頼るのは不可
                            mutex.ReleaseMutex();
                            Console.WriteLine("ID" + id + "が解放しました。");
                        }
                    }
                    else
                    {
                        Console.WriteLine("ID" + id + "は掴めませんでした。");
                    }
                });
            }
            Console.ReadKey();
        }
    }
}
 
【実行結果】
ID0が掴みました。
ID0が解放しました。
ID1が掴みました。
ID2は掴めませんでした。
ID1が解放しました。
 
--------------------------------
 
また、Mutexには、名前を付けることができます。
名前を付けることで、プロセス間でも排他制御が可能となります。
(ただし、意図せずMutex名が被ると意図しないロックがかかってしまうため、扱いには注意が必要です)
 
【サンプルコード】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MutexTest2
{
    class Program
    {
        static void Main(string[ ] args)
        {
            // 名前付きMutexだとプロセス間で共有できる
            //using (Mutex mutex = new Mutex(false))
            using (Mutex mutex = new Mutex(false,"hoge"))
            {
                // プロセス名取得(表示用)
                System.Diagnostics.Process p
                    = System.Diagnostics.Process.GetCurrentProcess();

                Console.WriteLine(DateTime.Now);
                Console.WriteLine(p.Id + "が掴もうとしています。");
                if (mutex.WaitOne(5000, false))
                {
                    try
                    {
                        Console.WriteLine(DateTime.Now);
                        Console.WriteLine(p.Id + "が掴みました。");
                        Thread.Sleep(10000);
                    }
                    finally
                    {
                        mutex.ReleaseMutex();
                        Console.WriteLine(DateTime.Now);
                        Console.WriteLine(p.Id + "が解放しました。");
                    }
                }
                else
                {
                    Console.WriteLine(DateTime.Now);
                    Console.WriteLine(p.Id + "は掴めませんでした。");
                }
            }
            Console.ReadKey();
        }
    }
}
 
【実行結果】
※exeファイルを3回起動した結果。
 1つ目のプロセス…掴んで解放
 2つ目のプロセス…解放を待って掴んで解放
 3つ目のプロセス…解放を待って掴めず諦める

2020/04/18 7:01:44
10420が掴もうとしています。
2020/04/18 7:01:44
10420が掴みました。
2020/04/18 7:01:54
10420が解放しました。

2020/04/18 7:01:50
2416が掴もうとしています。
2020/04/18 7:01:54
2416が掴みました。
2020/04/18 7:02:04
2416が解放しました。

2020/04/18 7:01:56
7524が掴もうとしています。
2020/04/18 7:02:01
7524は掴めませんでした。

C#:WPFでのHelloWorld

以前の記事では、WindowsFormでのHelloWorld(https://akira2kun.hatenablog.com/entry/2020/03/01/184434)を試しました。
今回の記事では、WPFでのHelloWorldを試してみます。
 
WPFはWindowsFormの後発にあたるWindows向け画面アプリの形式であり、画面がXAMLというマークアップ言語で記述されていること(デザイナーとプログラマーの分業がしやすい)、MVVM(単一スレッドしか操作できない、リストで表示されている分のオブジェクトしか生成されない、といったUIの制約を意識せずにビジネスロジックを書くための手法)を実現しやすいこと、が特徴となっています。
 
以下では、Visual Studio Communityを使用したHelloWorldの手順を紹介します。
操作感はWindowsFormと似ているので、注意する箇所だけ詳細に書きます。
XAMLの項目とクラスの値をバインドさせる(クラスの値が変更された際にXAMLの画面上に反映する)点がミソです。
 
【手順】
1.Visual Studio Community を開く。
2.「ファイル > スタートページ」でスタートページを表示させ、スタートページ上の「新しいプロジェクトを作成」をクリック。
3.「WPF アプリ」を選択する。名前は任意で良い。これで「OK」を押下すると、「場所」で指定した場所にプロジェクト(作業フォルダ)が生成される。
4.ボタンやラベルを配置する。すると以下のようなXAMLが生成される。
・MainWindow.xaml
<Window x:Class="HelloWorld3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld3"
        mc:Ignorable="d"
        Title="MainView" Height="450" Width="800">
    <Grid>
        <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Height="77" Margin="271,195,0,0" VerticalAlignment="Top" Width="239" Click="button_Click"/>
        <Label x:Name="label" Content="{Binding Text.Value}" HorizontalAlignment="Left" Height="33" Margin="319,88,0,0" VerticalAlignment="Top" Width="166"/>
    </Grid>
</Window>
 
5.下記のクラスを作成する。
・MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace HelloWorld3
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// Viewに相当
    /// </summary>
    public partial class MainWindow : Window
    {
        MainViewModel viewModel = new MainViewModel();

        public MainWindow()
        {
            InitializeComponent();
        }

        private void button_Click(object sender, RoutedEventArgs e)
        {
            // DataContextにMainViewModelオブジェクトををバインドさせる
            // そうすることで、{Binding Text.Value}で値を反映できる
            // DataContextは参照渡しできないのでreturnで受け取る
            DataContext = viewModel.TextRead(DataContext);
        }
    }
}
 
・MainViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Reactive.Bindings; // 自分で落としてくる必要がある

namespace HelloWorld3
{
    /// <summary>
    /// ViewModelに相当
    /// ModelとViewの間に入ることで、ModelがUIの制約を受けるのを防ぐ
    ///  制約の例…・単一スレッドしか操作できない
    ///       ・リストで表示されている分のオブジェクトしか生成されない
    /// </summary>
    class MainViewModel
    {
        MainModel model = new MainModel();

        public ReactiveProperty<string> Text { get; private set; }

        public object TextRead(object dataContext)
        {
            this.Text = model.Text;
            dataContext = this;
            return dataContext;
        }
    }
}
 
・MainModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Reactive.Bindings; // 自分で落としてくる必要がある

namespace HelloWorld3
{
    /// <summary>
    /// Modelに相当
    /// ビジネスロジックを記述する。
    /// </summary>
    class MainModel
    {
        public ReactiveProperty<string> Text { get; private set; }
                = new ReactiveProperty<string>();

        public MainModel()
        {
            Text.Value = "Hello World!";
        }
    }
}
 
※補足
バインドさせる際には、「ReactiveProperty」を使用すると便利です。
自分で「NuGet パッケージの管理」から落とす必要があります。
f:id:akira2kun:20200412154620j:plain

 
【実行結果】
ボタン押下で「Hello World!」が表示されます。
f:id:akira2kun:20200412154749j:plainf:id:akira2kun:20200412154814j:plain

java:並行開発を行うためのテクニック

何も考えずにクラス分割を行いクラス毎に開発者を割り当てると、未完成のクラスを取り込むことができず、他のクラスが完成するのを待つ必要が出てきてしまいます。
今回は、他のクラスが完成するのを待たずに並行開発するテクニックを、javaの例を用いて説明します。
 
----------------------------
 
下記は何も考えずにクラス分割した場合の例です。
HelloProductクラスを取り込みたいのですが、HelloProductクラスが完成していないため、コンパイルができず、HelloProductクラスが完成するまで実行確認を行うことができません。
 
【サンプルコード】
・HelloMain.java(新規)
public class HelloMain {

    public static void main(String[ ] args) {
        HelloProduct helloProduct = new HelloProduct();
        helloProduct.print();
    }

}
 
【実行結果】
HelloProductクラスが作成されていないためにコンパイルエラー。
 
----------------------------
 
そこで、Interfaceを用いる手があります。
Interfaceの下に、最終的に取り込みたいクラス(HelloProductクラス)と、実行確認時に暫定的に取り込むクラス(HelloTestクラス)を定義することで、最終的に取り込みたいクラスの完成を待たずに、実行確認時に暫定的に取り込むクラスを用いてテストが可能になります。
下記は、暫定的にクラスを取りこんで実行確認を行う例です。
 
【サンプルコード】
・HelloMain.java(修正)
public class HelloMain {

    public static void main(String[ ] args) {
        HelloInterface hello = new HelloTest();
        hello.print();
    }

}
 
・HelloInterface.java(新規)
public interface HelloInterface {

    public abstract void print();

}
 
・HelloTest.java(新規)
public class HelloTest implements HelloInterface {

    public void print() {
        System.out.println("Hello World! Test!");
    }

}
 
【実行結果】
Hello World! Test!
 
----------------------------
 
そして、最終的に取り込みたいクラスが完成したら、newの箇所を変更することでそのクラスの取り込みが可能になります。
 
【サンプルコード】
・HelloMain.java(修正)
public class HelloMain {

    public static void main(String[ ] args) {
        HelloInterface hello = new HelloProduct();
        hello.print();
    }

}
 
・HelloProduct.java(新規)
public class HelloProduct implements HelloInterface {

    public void print() {
        System.out.println("Hello World! Product!");
    }

}
 
【実行結果】
Hello World! Product!
 
----------------------------
 
以上のようにInterfaceの機能を用いて並行開発を行うのは、実際の開発でもよく使われる手段です。
 
補足すると、この手段は若干古典的な手段となります。
より発展的な手段としては、DI(Dependecy Injection)という手段があり、SpringFramework等のフレームワークの機能を用いることで容易に実現することが可能になります。
DIは、newの箇所(上記のサンプルコードで「 = new HelloTest();」や「 = new HelloProduct();」としていた箇所)の定義を何かしらの方法で不要とすることで、ソースコードを変更することなく取り込むクラスを変更できるようにする、という手段です。
SpringFrameworkでは、特定のアノテーションソースコードに付与することで、取り込むクラスをxmlファイルで指定することが可能になります。上記のサンプルコードの例で言うと、HelloTestクラスを取り込むかHelloProductクラスを取り込むのかをxmlファイルに定義することが可能になります。
 
DIについてはこの記事では詳しく触れませんが、現在の開発ではDIが使われることも多く、書籍やWebの文献も豊富です。
実際に使おうと思った時に実装方法がわからずに困る、ということは少ないでしょう。

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

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