- 投稿日:2020-09-09T19:27:35+09:00
プロセスを監視して、1秒ごとに、使用中のアプリと、操作中か否かのログを書き出す、ツール
完全にディストピア感があるが、「プロセスを監視して、1秒ごとに、使用中のアプリと、操作中か否かのログを書き出す、ツール」を作った。
(毎日毎日、何をやったかを業務日報を記録する必要がある現場で、毎回手入力をするのがアホくさいので、「わかった。もう全部ログ差し出すから勝手に分析して」という気持ちで、ツール作った。)
ツール作ってみて、自身で計測してみたら、8時間ぐらいの労働時間中、PC操作(キーボードかマウス操作)しているのは、3-4割ぐらいだった。そんなもんかな、とは思った。
※c#のprocessクラスではなくwinapiを利用しているのは、c#の純正では、うまくプロセス名を拾えないケースがあったため
Process_Kanshi.cs//c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe Process_Kanshi.cs using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Collections.Generic; using System.Text; public class lCnt{ public int cntWrk { get; set; } public int cntSm { get; set; } public string PNm { get; set; } public string MWT { get; set; } public override string ToString() { return cntWrk + "\t" + cntSm + "\t" + PNm + "\t" + MWT; } public void toWin(){ this.cntWrk++; this.cntSm++; } public void toLose(){ this.cntSm++; } } public class Process_Kanshi { ///* [DllImport("user32.dll")] public static extern int GetAsyncKeyState(long vKey); [DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)] static extern void SetCursorPos(int X, int Y); [DllImport("kernel32.dll")] private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr handle); [DllImport("USER32.DLL")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll", SetLastError = true)] private static extern int GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("psapi.dll", CharSet = CharSet.Ansi)] private static extern uint GetModuleBaseName(IntPtr hWnd, IntPtr hModule, [MarshalAs(UnmanagedType.LPStr), Out] StringBuilder lpBaseName, uint nSize); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int GetWindowTextLength(IntPtr hWnd); public static void Main(string[] args) { ///** //Process[] processList; DateTime dt = DateTime.Now; string dt_s = dt.ToString("yyyy_MM_dd"); bool DtMsCr = false; bool DtKyBd = false; bool isWin = false; string s_mc = ""; string lgT = ""; string pT = ""; string pnm0 = ""; string mwt0 = ""; Dictionary<string, lCnt> prcLog;// = new Dictionary<string, lCnt>(); for(;;){ ///*** prcLog = new Dictionary<string, lCnt>(); for(int i = 0 ; i<60*5; i++){ //**** //--------- DtMsCr = false; DtKyBd = false; s_mc = Control.MousePosition.ToString(); pnm0 = ""; mwt0 = ""; pT = ""; isWin = false; //--------- DateTime mdt1 = DateTime.Now; //processList = Process.GetProcesses(); IntPtr hWnd = GetForegroundWindow(); uint processId; GetWindowThreadProcessId(hWnd, out processId); if (hWnd != IntPtr.Zero) { var hnd = OpenProcess(0x0400 | 0x0010 , false, processId); var buffer = new StringBuilder(255); GetModuleBaseName(hnd, IntPtr.Zero, buffer, (uint)buffer.Capacity); int textLen = GetWindowTextLength(hWnd); StringBuilder tsb = new StringBuilder(textLen + 1); GetWindowText(hWnd, tsb, tsb.Capacity); var processName = buffer.ToString().ToLower(); //----- isWin = true; string lmwt = tsb.ToString(); lmwt = lmwt.Trim().Replace(" ",""); lmwt = lmwt.Substring(0,Math.Min(30,lmwt.Length)); pT = processName + "_" + lmwt; Console.WriteLine( pT ); pnm0 = processName; mwt0 = lmwt; //----- } if(!isWin && (int)hWnd!=0){ Console.WriteLine( "Unknown" + " _ " + hWnd); pT = "Unknown"; pnm0 = "Unknown"; } if((int)hWnd==0){ Console.WriteLine( "LOG_OFF" + " _ " + hWnd); pT = "LOG_OFF"; pnm0 = "LOG_OFF"; } for(int ix = 32 ; ix <= 226; ix ++){ if(GetAsyncKeyState(ix) != 0){ DtKyBd = true; } } if(s_mc != Control.MousePosition.ToString()){ DtMsCr = true; } //////////////############## if (!prcLog.ContainsKey(pT)) { prcLog.Add(pT, new lCnt() { cntWrk=1, cntSm=1, PNm = pnm0, MWT = mwt0 }); }else{ if(DtKyBd || DtMsCr){ prcLog[pT].toWin(); //Console.WriteLine( "w"); }else{ prcLog[pT].toLose(); //Console.WriteLine( "l"); } } //////////////############## DateTime mdt2 = DateTime.Now; TimeSpan mms = mdt2-mdt1; int imms = (int)mms.TotalMilliseconds; //Console.WriteLine( "imms "+ imms); imms = 1000 - imms -10 ; if(imms>=1000 || imms < 0 ){imms=700;} System.Threading.Thread.Sleep(imms); } //**** dt = DateTime.Now; string dt_sl = dt.ToString("yyyy_MM_dd_HHmmss"); lgT = ""; foreach (KeyValuePair<string, lCnt> kvp in prcLog) { lgT = lgT + dt_sl + "\t" + kvp.Key + "\t" + kvp.Value + Environment.NewLine; } System.IO.File.AppendAllText(@".\" + dt_s + ".txt", lgT); lgT = ""; pT = ""; pnm0 = ""; mwt0 = ""; DtMsCr = false; DtKyBd = false; s_mc = Control.MousePosition.ToString(); } ///*** } ///** } ///*
- 投稿日:2020-09-09T12:56:45+09:00
【C#】競技プログラミング 入出力処理
入力
文字列1行
string str = Console.ReadLine();int配列
int[] array = Console.ReadLine().Split().Select(int.Parse).ToArray();long配列
long[] array = Console.ReadLine().Split().Select(long.Parse).ToArray();
Console.ReadLine().Split()
までの部分で関数を作っておくと良いかもしれません。N Mなどの複数の時
以下のクラスをコピペして貼る。
(atcoder社長のchokudai氏の利用しているScannerを一部改変したもの)C#は普通に競技プログラミングで世界1位十分狙えるポテンシャルのある言語だと思うので、そのまま続けてくださいな!
— chokudai(高橋 直大)??? (@chokudai) October 15, 2018
もし入出力とかの煩わしさが気になってたら、この提出の下の方にある適当に作ったScannerクラスをコピペして使ってもらえば大丈夫ですよ!https://t.co/TWyP6gwGfNclass Scanner { string[] s; int i; char[] cs = new char[] { ' ' }; public Scanner() { s = new string[0]; i = 0; } public string Next() { if (i < s.Length) return s[i++]; string st = Console.ReadLine(); while (st == "") st = Console.ReadLine(); s = st.Split(cs, StringSplitOptions.RemoveEmptyEntries); if (s.Length == 0) return Next(); i = 0; return s[i++]; } public int NextInt() { return int.Parse(Next()); } public long NextLong() { return long.Parse(Next()); }使い方
var scanner = new Scanner(); //N M K int N = NextInt(); long M = NextLong(); string K = Next();出力
基本
//改行も入ります var num = 20; Console.WriteLine(num); Console.WriteLine("Yes"); Console.WriteLine("No");フォーマット
var n = 12, m = 24; Console.WriteLine("{0} {1} {2}", n, m, 4 + 4);変数名直接(古いコンパイラでは対応していないかも)
var n = 12, m = 24; Console.WriteLine("${n} ${m}");配列
var array = new int[]{ 20, 30, 22, 11 }; //コンマ区切り Console.WriteLine(string.Join(",", array)); //スペース区切り Console.WriteLine(string.Join(" ", array)); //改行区切り Console.WriteLine(string.Join("\r\n", array));foreachを使ったやつ。atcoderだと、一番最後に空白あっても問題ないため
var array = new int[]{ 20, 30, 22, 11 }; foreach(var a in array) { Console.WriteLine("{0} ", a); }
- 投稿日:2020-09-09T09:32:58+09:00
XAML構築時に利用したやつ簡易メモ
XAMLでWPF用のUIを構築する際のメモ
検索して探した結果などのコピペ的まとめもありまするメモAPPのウィンドウ枠を非表示にして透明化
APPを閉じたりするバーなどを非表示にしてAPPの枠がない状態へ設定
WindowStyle : None
AllowTransparency : trueカスタムコントロールからAPPの閉じるなどのバーを自作
自作したAPPヘッダーでAPP拡大や移動など制御したい
カスタムコントロールにするとWindowがないから動かなかったので対処をメモpublic static class WindowUtil { public static Window GetWindow(this DependencyObject element) { return Window.GetWindow(element); } }private void OnClose(object sender, RoutedEventArgs e) { var window = ((FrameworkElement)sender).GetWindow(); window.Close(); } private void OnMove(object sender, MouseButtonEventArgs e) { var window = ((FrameworkElement)sender).GetWindow(); window.DragMove(); } private void OnMaximam(object sender, RoutedEventArgs e) { var window = ((FrameworkElement)sender).GetWindow(); window.WindowState = window.WindowState != WindowState.Maximized ? WindowState.Maximized : WindowState.Normal; } private void OnMimimam(object sender, RoutedEventArgs e) { var window = ((FrameworkElement)sender).GetWindow(); window.WindowState = window.WindowState != WindowState.Minimized ? WindowState.Minimized : WindowState.Normal; }ボタンの見た目を変更
カンバスにSVGみたいな言語で図形が描画できる
このへんは便利と感じる、SVGと同じことできる感じ<Style TargetType="Button"> <!--Set to true to not get any properties from the themes.--> <Setter Property="OverridesDefaultStyle" Value="True"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Grid> <Ellipse Fill="{TemplateBinding Background}"/> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>カスタムコントロールのXAMLでへ値をプロパティ譲渡
public class ClockControl: UserControl { public static readonly DependencyProperty CityProperty = DependencyProperty.Register ( "City", typeof(string), typeof(ClockControl), new PropertyMetadata(string.Empty) ); public string City { get { return (string)GetValue(CityProperty); } set { SetValue(CityProperty, value); } } public ClockControl() { InitializeComponent(); } }グリッドレイアウトでレスポンシブ
*がついているやつは伸びる感じします
<Grid> <Grid.RowDefinitions> <RowDefinition Height="1080*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="240*"/> <ColumnDefinition Width="240*"/> <ColumnDefinition Width="240*"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0"></StackPanel> <StackPanel Grid.Column="1"></StackPanel> <StackPanel Grid.Column="3"></StackPanel> </Grid>参考サイト
XAML でのレスポンシブ レイアウト
https://docs.microsoft.com/ja-jp/windows/uwp/design/layout/layouts-with-xamlControlTemplate クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.controls.controltemplate?view=netcore-3.1How to: Draw an Ellipse or a Circle
https://docs.microsoft.com/ja-jp/dotnet/desktop/wpf/graphics-multimedia/how-to-draw-an-ellipse-or-a-circle?view=netframeworkdesktop-4.8ウィンドウ枠のない WPF アプリを作成する
https://sakapon.wordpress.com/2015/03/01/wpf-borderless/【WPF】包含するコンテンツからWindowを取得するには?
http://pro.art55.jp/?eid=1070343
- 投稿日:2020-09-09T06:29:22+09:00
IvyFEM(.NET向け有限要素法ライブラリ)をPythonから使用する
1. はじめに
.NET向け有限要素法ライブラリIvyFEM.dllを開発中です。
C#で利用することを想定したライブラリですが、Pythonからも利用できそうなことがわかったので記事にします。2. Windows10でPythonから.NETを呼び出す方法(Python.NET)
Python.NETを使うことにしました。
http://pythonnet.github.io/以下、Windows環境にPythonが既にインストールされていることとします。
2.1. Python.NETのインストール
コマンドプロンプトから以下を実行してください。
py -m pip install pythonnet3. IvyFEM.dllパッケージ
GithubのIvyFEMページから最新のパッケージをダウンロードして、作業ディレクトリに展開してください(IvyFEMページのインストール方法を参照してください、VC++のruntimeが必要)。
IvyFEM: https://github.com/ryujimiya/IvyFEM/4. PythonからIvyFEMを呼び出す
PythonでPoissonの方程式を解いてみました。
正方形領域をとり、辺上はポテンシャル0の境界条件を課しています。
領域内部に円の領域をとり、その領域に電荷を印加したときの領域全体のポテンシャル分布を計算しています。
main2.pyimport clr clr.AddReference("System") from System import UInt32 clr.AddReference("System.Collections") from System.Collections.Generic import List, IList clr.AddReference('IvyFEM') from IvyFEM \ import \ Cad2D, Mesher2D, FEWorld, \ FiniteElementType, \ PoissonMaterial, \ CadElementType, FieldValueType, FieldFixedCad, \ Poisson2DFEM clr.AddReference('IvyFEM') from IvyFEM.Linear \ import \ LapackEquationSolver, LapackEquationSolverMethod, \ IvyFEMEquationSolver, IvyFEMEquationSolverMethod clr.AddReference('OpenTK') from OpenTK import Vector2d cad = Cad2D() pts = List[Vector2d]() pts.Add(Vector2d(0.0, 0.0)) pts.Add(Vector2d(1.0, 0.0)) pts.Add(Vector2d(1.0, 1.0)) pts.Add(Vector2d(0.0, 1.0)) lId1 = cad.AddPolygon(pts).AddLId print("lId1 = " + str(lId1)) # ソース lId2 = cad.AddCircle(Vector2d(0.5, 0.5), 0.1, lId1).AddLId print("lId2 = " + str(lId2)) eLen = 0.05 mesher = Mesher2D(cad, eLen) # 座標を参照 vecs = mesher.GetVectors() cnt = len(vecs) print("len(vecs) = " + str(cnt)) coId = 0 for vec in vecs: print("vec[" + str(coId) + "] " + str(vec.X) + ", " + str(vec.Y)) coId += 1 world = FEWorld() world.Mesh = mesher dof = 1 # スカラー feOrder = 1 quantityId = world.AddQuantity(dof, feOrder, FiniteElementType.ScalarLagrange) world.ClearMaterial() ma1 = PoissonMaterial() ma1.Alpha = 1.0 ma1.F = 0.0 ma2 = PoissonMaterial() ma2.Alpha = 1.0 ma2.F = 100.0 maId1 = world.AddMaterial(ma1) maId2 = world.AddMaterial(ma2) lId1 = 1 world.SetCadLoopMaterial(lId1, maId1) lId2 = 2 world.SetCadLoopMaterial(lId2, maId2) zeroEIds = [1, 2, 3, 4] zeroFixedCads = List[FieldFixedCad]() for eId in zeroEIds: #スカラー # オーバーロードであることに注意 fixedCad = FieldFixedCad.Overloads[UInt32, CadElementType, FieldValueType](eId, CadElementType.Edge, FieldValueType.Scalar) zeroFixedCads.Add(fixedCad) world.SetZeroFieldFixedCads(quantityId, zeroFixedCads) #DEBUG zeroFixedCads = world.GetZeroFieldFixedCads(quantityId) print("len(zeroFixedCads) = " + str(len(zeroFixedCads))) world.MakeElements() feIds = world.GetTriangleFEIds(quantityId) feCnt = len(feIds) print("feCnt = " + str(feCnt)) for feId in feIds: print("--------------") print("feId = " + str(feId)) triFE = world.GetTriangleFE(quantityId, feId) nodeCoIds = triFE.NodeCoordIds elemNodeCnt = nodeCoIds.Length for iNode in range(elemNodeCnt): print("coId[" + str(iNode) + "] = " + str(nodeCoIds[iNode])) # ポアソン方程式FEM FEM = Poisson2DFEM(world) # リニアソルバー ''' solver = LapackEquationSolver() solver.IsOrderingToBandMatrix = True solver.Method = LapackEquationSolverMethod.Band FEM.Solver = solver ''' solver = IvyFEMEquationSolver() solver.Method = IvyFEMEquationSolverMethod.NoPreconCG FEM.Solver = solver # 解く FEM.Solve() U = FEM.U # 結果 nodeCnt = len(U) for nodeId in range(nodeCnt): print("-------------") coId = world.Node2Coord(quantityId, nodeId) value = U[nodeId] print(str(nodeId) + " " + str(coId) + " " + "{0:e}".format(value))5. 実行
py -m main25. まとめ
IvyFEM.dllを使ってPythonからPoissonの方程式を解きました。
- 投稿日:2020-09-09T02:03:42+09:00
【C#】インターフェイスの利点が理解できない人は「インターフェイスには3つのタイプがある」ことを理解しよう
はじめに
C#を始めとするオブジェクト指向言語には「インターフェイス」という機能があります。
これを使うと良い設計になるというのはよく言われていますが、具体的にインターフェイスを使うとどう良いことがあるのか、というのは実感しづらい人も多いと思います。僕もC#学びたての頃はほんとうにインターフェイスの利点が理解できず苦しみました。しかし、この記事で説明する「インターフェイスには3つのタイプがある」ことを理解して以来、もうインターフェイスが便利すぎて、インターフェイスなしではコーディングできない体質になってしまいました。
そこでこの記事では、インターフェイスを使う利点がいまいち理解できていない人が、インターフェイスを使いたくて使いたくて仕方がなくなるようにすることを目的として書きました。
注意点として、僕はC#の開発者でもなければ指導者でもないので、あくまで個人的な意見として参考にしていただけるとうれしいです。
間違っていたり、意見がお有りの方は、ぜひコメントでお知らせください。インターフェイスについて「よくある間違い」
インターフェイスについて、以下のような間違いがよくされています。
- インターフェイスは、ポリモーフィズムを実現するためだけに存在する
- インターフェイスは、複数のクラスに実装しないと意味がない
僕もC#学びたての頃はよくこのような勘違いをしていて、そのせいでまったく理解が進みませんでした。
しかし、これらは間違いです。インターフェイスは、これによってポリモーフィズムが実現されなくても大きな意味があるし、たった一つのクラスにしか実装されないインターフェイスにも、重要な意味があります。
これらの混乱が生じる原因として、一言に「インターフェイス」と言っても3つのタイプが存在するからではないかと考えました。
3つのタイプのインターフェイス
インターフェイスは、以下の3つのタイプに分けられます。
1. 疎結合を目的としたインターフェイス
2. クラスの機能を保証することを目的としたインターフェイス
3. クラスへの安全なアクセスを提供することを目的としたインターフェイスコードだけを見ていると、どのインターフェイスも同じように使われているように見えますが、実はその目的は全く異なることがあるわけです。
よくあるインターフェイスの説明は分かりづらい
また、インターフェイスの説明は分かりづらいものが多すぎます。
「C# インターフェイス とは」などで調べると出てくるよくある説明に次のようなものがあります。「インターフェイスは、実装するクラスにメソッドの実装を強制するものです」
これがよくわからないんですよね。
メソッドの実装を強制されるとどんないいことがあるのかわかりません。また、「インターフェイス」という名前からも想像がつかない機能というか。
なぜ「メソッドの実装を強制する」という機能に「インターフェイス」という名前が付けられているのかわかりません。これは、上記の「インターフェイスは、実装するクラスにメソッドの実装を強制するものです」という説明が、インターフェイスの本質を説明したものではないから、混乱が生じるんだと思います。
じゃあインターフェイスの本質ってなに
インターフェイスとは「インターフェイス」です。
「ユーザーインターフェイス」とかの「インターフェイス」と同じ意味です。では何のインターフェイスかというと、クラスのインターフェイスです。
もっと言うと、「クラスにアクセスするためのインターフェイス」といえます。「クラスにアクセスするためのインターフェイス?別にインターフェイスがなくても普通に
インスタンス名.メンバ名
でアクセスできるが?」と思うかもしれません。
ところが、インターフェイスがないと大きな問題となる場合がたくさんあります。QiitaPostクラスで考えてみる
例えば以下のような、Qiitaの記事を表す
QiitaPost
クラスを考えてみます。class QiitaPost { private string m_title; private string m_text; /// <summary> /// タイトルと本文を入力して、記事を新規作成します。 /// </summary> /// <param name="title">記事タイトル</param> /// <param name="text">記事本文</param> public QiitaPost(string title, string text) { this.m_title = title; this.m_text = text; } /// <summary> /// 記事のURL /// </summary> public Uri PostURL { get; } /// <summary> /// 記事タイトル /// </summary> public string Title => m_title; /// <summary> /// 記事本文 /// </summary> public string Text => m_text; /// <summary> /// LGTM数 /// </summary> public int LGTMCount { get; private set; } /// <summary> /// ストック数 /// </summary> public int StockCount { get; private set; } /// <summary> /// LGTMする /// </summary> public void LGTM() { ++LGTMCount; } /// <summary> /// ストックする /// </summary> public void Stock() { ++StockCount; } /// <summary> /// 記事を削除する /// </summary> public void Delete() { m_title = string.Empty; m_text = string.Empty; } }Qiitaの記事を書く人
Qiitaはまず記事がなきゃ始まりません。
記事を書く人がこのQiitaPost
クラスのインスタンスを生成しました。//Qiitaの記事を書いた! QiitaPost post = new QiitaPost("タイトル", "本文");これをQiitaのサーバーにアップロードします。
//Qiitaのサーバーに記事をアップロードした! QiitaServer.Upload(post);これで晴れてみんなに読んでもらえます。
Qiitaの記事を読む人
Qiitaの記事を読む人は、
QiitaServer
から記事を取得してタイトルと本文を確認します。//Qiita記事を取得 QiitaPost downloadedPost = QiitaServer.Download("https://~"); //取得した記事のタイトルと本文を読む string title = downloadedPost.Title; string text = downloadedPost.Text; //記事をLGTMする downloadedPost.LGTM();記事を読んだあとに良かったと思ったのでLGTMもしちゃいました。
問題点
さて、この
QiitaPost
クラスの問題点は、次のようなことができてしまう点です。//Qiita記事を取得 QiitaPost downloadedPost = QiitaServer.Download("https://~"); //取得した記事のタイトルと本文を読む string title = downloadedPost.Title; string text = downloadedPost.Text; //記事をLGTMする downloadedPost.LGTM(); //勝手に人の書いた記事を消す!! downloadedPost.Delete();なんと、読み手が勝手に人の書いた記事を消せてしまいます。
そりゃそうですよね、何しろQiitaPost
クラスにはDelete
メソッドがpublic
で定義されているのですから…。また、次のような問題も発生します。
//記事を書く QiitaPost post = new QiitaPost("タイトル", "本文"); //記事をアップロードする QiitaServer.Upload(post); //自分の記事にLGTMする!! post.LGTM();自分の記事に自分でLGTMできちゃいます。
なぜこのような問題が起きるのか?
なぜこのような問題が起きるのかというと、ズバリ「
QiitaPost
クラスにアクセスするための適切なインターフェイスが定義されていないから」に尽きると思います。確かに、インターフェイスがなくても
QiitaPost
クラス自体にはインスタンス名.メンバ名
でアクセスできます。
アクセスできますというか、誰でも彼でもフリーでアクセスし放題です。自販機で言えば、「売上金をすべて排出する」ボタンが客が触れられる場所に配置してあるようなものです。
普通の自販機は客用のインターフェイスと管理者用のインターフェイスが完全に分けられ、客は「売上金をすべて排出する」ボタンを押すことはできません。それと同じで、クラスにも使用者に応じて適切なインターフェイスが定義されていないと、あとあと問題が発生することがあります。
これが、C#の「インターフェイス」の本質的な意味です。
…と僕は思います。QiitaPostクラスにインターフェイスを定義してみる
では、実際に
QiitaPost
クラスのインターフェイスを定義して、先ほどの問題が発生しないようにしてみます。記事を投稿する人には次の機能が必要でしょうか。
/// <summary> /// 記事投稿者用のQiitaPostインターフェイス /// </summary> interface IAuthorQiitaPost { /// <summary> /// LGTM数を取得する /// </summary> int LGTMCount { get; } /// <summary> /// ストック数を取得する /// </summary> int StockCount { get; } /// <summary> /// 記事を削除する /// </summary> void Delete(); }記事を閲覧する人には次のようなインターフェイスを用意しました。
/// <summary> /// 記事閲覧者用のQiitaPostインターフェイス /// </summary> interface IReaderQiitaPost { /// <summary> /// 記事タイトルを取得する /// </summary> string Title { get; } /// <summary> /// 記事の本文を取得する /// </summary> string Text { get; } /// <summary> /// LGTM数を取得する /// </summary> int LGTMCount { get; } /// <summary> /// ストック数を取得する /// </summary> int StockCount { get; } /// <summary> /// 記事にLGTMする /// </summary> void LGTM(); /// <summary> /// 記事をストックする /// </summary> void Stock(); }おっと、ここで被っているメンバがありますね。
被っているメンバは更に抽象的なIQiitaPost
インターフェイスにまとめてしまいましょう。interface IQiitaPost { /// <summary> /// LGTM数を取得する /// </summary> int LGTMCount { get; } /// <summary> /// ストック数を取得する /// </summary> int StockCount{ get; } }それに伴って、
IAuthorQiitaPost
インターフェイスとIReaderQiitaPost
インターフェイスも次のように変更します。/// <summary> /// 記事投稿者用のQiitaPostインターフェイス /// </summary> interface IAuthorQiitaPost : IQiitaPost { /// <summary> /// 記事を削除する /// </summary> void Delete(); } /// <summary> /// 記事閲覧者用のQiitaPostインターフェイス /// </summary> interface IReaderQiitaPost : IQiitaPost { /// <summary> /// 記事タイトルを取得する /// </summary> string Title { get; } /// <summary> /// 記事の本文を取得する /// </summary> string Text { get; } /// <summary> /// 記事にLGTMする /// </summary> void LGTM(); /// <summary> /// 記事をストックする /// </summary> void Stock(); }とてもすっきりしました。
QiitaPostクラスにインターフェイスを実装する
では早速、作成したインターフェイスをQiitaPostクラスに実装させます。
といっても、すでに内部実装はされているのでクラス定義のところにインターフェイス名を書くだけですね。class QiitaPost : IAuthorQiitaPost, IReaderQiitaPost { //~略~ }今回はインターフェイスに定義されたすべてのメンバが、すでに
QiitaPost
クラスに実装されているのでエラーが出ませんが、一つでも実装されていないメンバがあるとコンパイルエラーになります。
これが冒頭で説明した「インターフェイスは、実装するクラスにメソッドの実装を強制するものです」という説明に通じるわけですね。QiitaPostクラスにインターフェイスを通じてアクセスさせる
それでは、インターフェイスを作成したので、「Qiitaの記事を作成する人」と「Qiitaの記事を読む人」にはQiitaPostクラスに直接アクセスするのをやめてもらい、きちんとインターフェイス経由でアクセスしてもらいましょう。
//記事を書く IAuthorQiitaPost post = new QiitaPost("タイトル", "本文"); //記事をアップロードする QiitaServer.Upload(post); //自分の記事にLGTMできない〜〜〜 //post.LGTM();
post
をIAuthorQiitaPost
型で定義していますので、自分で自分の記事にLGTMできません。
なぜなら、IAuthorQiitaPost
インターフェイスのメンバに、LGTM
メソッドが存在しないからです。これで、記事を書いた人は自分でLGTMするとか余計なことができなくなり、おかしな使い方をされることはなくなりました。
続いて、記事を読む人にもインターフェイス経由で読んでもらいます。
//Qiita記事を取得 IReaderQiitaPost downloadedPost = QiitaServer.Download("https://~"); //取得した記事のタイトルと本文を読む string title = downloadedPost.Title; string text = downloadedPost.Text; //記事をLGTMする downloadedPost.LGTM(); //勝手に人の書いた記事を消せない〜〜〜 //downloadedPost.Delete();きちんと読む人専用の
IReaderQiitaPost
インターフェイス経由で読んでもらうことで、勝手に人の記事を消すなどという酷いことはできなくなりました。このように、クラスを作るときには「誰に、どのように使ってほしいか」を意識した上で、適切にインターフェイスを用意することがとても重要です。
適切なインターフェイスを用意し、使う側がきちんと然るべきインターフェイス経由でクラスにアクセスするようにすることで、想定外の使い方をされて不具合が発生することを防ぐことができます。クラスを作る前にインターフェイスから作る
もっと言えば、クラスを作成する前にまずインターフェイスから設計することが好ましいと思います。
クラスの使い手と使い方を意識して、まずクラスのインターフェイスを作ります。
それが終わったあとに、クラスに実装させて、エラーが出なくなるまで内部の実装を書くという流れを心がけると、うっかりクラスに直接アクセスされてしまった!なんてことが少なくなると思います。また、インターフェイスの設計は、クラス内部でどうやって実装しようかなどと考えることもなく、必要な機能をただ列挙していくだけなので、必要な機能を抜かりなく記述することができるというメリットもあります。
たとえば、今回の例だと
IAuthorQiitaPost
インターフェイスを設計するときに、「あれ、削除するだけじゃなくて編集する機能もいるな」と気づきやすくなると思います。インターフェイスの「3タイプ」解説
さて、ここからが本題です。
タイトルで、「インターフェイスには目的に応じて3つのタイプがある」と書きました。
もう一度書くと次の3タイプです。1. 疎結合を目的としたインターフェイス
2. クラスの機能を保証することを目的としたインターフェイス
3. クラスへの安全なアクセスを提供することを目的としたインターフェイス前項で扱ったインターフェイスは、このうちどれにあたるでしょうか?
もうおわかりですね、「3.クラスへの安全なアクセスを提供することを目的としたインターフェイス」にあたります。
では、他の2つはどんなインターフェイスなのかを解説していきます。
1. 疎結合を目的としたインターフェイス
1つ目は疎結合を目的として作られるインターフェイスです。
例えば次のようなものがあります。//疎結合を目的として作られたインターフェイス interface ITextReader { string Read(string path); } class TextReader : ITextReader { public string Read(string path) { //テキストファイルを読み込んだ結果を返す処理 } }この例の
ITestReader
インターフェイスは、疎結合を目的として作られたインターフェイスです。利点1.クラスへの結合を弱くして変更に強くなる
使い手が、クラスに直接アクセスせずにインターフェイス経由でアクセスさせることで、クラス同士の結合度を下げることが目的です。
このようにしておくことで、たとえTextReader
クラスに変更が生じたとしても、TextReader
クラスがITextReader
インターフェイスを実装している限り、User
側の変更は不要になります。利点2.クラスへの結合が弱いので機能追加も容易になる
例えば、実際にテキストファイルを読み込むのではなく、デバッグ用に用意したダミーデータを読み込ませたいとします。
このとき、ITextReader
インターフェイスがあるおかげで、以下のようにすることができます。
ITextReader
インターフェイスを実装したDebugTextReader
クラス新たに登場しています。
しかし、ユーザー側はあくまでITextReader
インターフェイスにアクセスしています。User側はただ
ITextReader
だけを知っていて、その参照先が具体的にどのクラスなのかは知りませんから、これまたUser
側の変更は不要になるのです。このタイプのインターフェイスがないと変更がダイレクトに影響する
以下のように、インターフェイスを用意せずにクラス同士をダイレクトにアクセスさせると、
User
クラスがTextReader
クラスの変更の影響をダイレクトに受けるようになります。
例えば、
TextReader
クラスのRead
メソッドの名前がLoad
に変わったとします。class TextReader { public string Load(string path) { //テキストファイルを読み込んだ結果を返す処理 } }これだけで、
TextReader
クラスを使っているUser
側はRead
→Load
への変更を余儀なくされます。
一つのクラスなら良いですが、これがたくさんのクラスから依存されていた場合、影響するすべてのコードを変更しなければなりません。このようなことにならないよう、インターフェイスを用意しておくことで、
TextReader
クラスは必ずITextReader
インターフェイスに準拠した実装にならなければなりません。
つまり、ITextReader
クラスを実装してさえいれば、ITextReader
インターフェイス経由でアクセスしている他のクラスへの影響はまったくなくなるのです。どうでしょう。このタイプのインターフェイスの利点がおわかりいただけたでしょうか。
理想は1クラスにつき少なくとも1インターフェイス
どのようなクラスにも必ずアクセス用のインターフェイスを用意しておくことが理想と思います。
面倒と思わずに、インターフェイスを用意しておくだけで、万が一の変更があったときに大いに役立ってくれるでしょう。2. クラスの機能を保証することを目的としたインターフェイス
続いて、「クラスの機能を保証することを目的としたインターフェイス」について説明します。
このタイプのインターフェイスは、記事の序盤で「よくあるインターフェイスの説明」として挙げた「インターフェイスは、実装するクラスにメソッドの実装を強制するもの」という説明を受けたとしても最も納得しやすいタイプです。
要は、インターフェイスを実装したクラスは、そのインターフェイスに定義されたメソッドは必ず実装されるのだから、特定の機能があることが保証されますよ、ということですね。
このタイプのインターフェイスとして、有名なものがいくつかありますので列挙します。
インターフェイス 保証する機能 IEnumerable
foreachで回すことができる IEqautable
値の等価性を評価することができる IDisposable
明示的にメモリを開放することができる IObservable
クラスからの通知を受け取ることができる 例えば、
IEnumerable<T>
インターフェイスを実装したクラスは、IEnumerator<T>
を返すGetEnumerator()
メソッドの実装を強制されるので、foreachステートメントで回すことができることが保証されます。
IEquatable<T>
インターフェイスを実装したクラスは、bool Equals(T other)
メソッドの実装を強制されるので、他のオブジェクトとの等価性を比較できることが保証されます。このように、クラスに一定の機能があることを保証するために使われるインターフェイスが、このタイプです。
また、クラスの使い手側も、「あ、このクラスは
IEnumerable
だからforeachで回せるな」「このクラスはIDisposable
だから使い終わったらDispose
しなくちゃいけないんだな」などと、クラスの定義を見ただけでそのクラスの性質を簡単に理解できることも利点の一つですね。もちろんポリモーフィズムによる利点も
もちろん、利点はそれだけではなく、ポリモーフィズムを利用した利点もあります。
例えば
IEnumerable<T>
インターフェイスは、List<T>
,Dictionary<TKey, TValue>
,T[]
など、様々なクラスが実装しているので、IEnumerable<T>
型の変数には、それを実装した様々なクラスを受けることができます。例えばメソッドの引数として、
IEnumerable
で受けておけば、使う側はそこに代入できるインスタンスの選択肢が大幅に増えるわけです。
逆に、メソッドの引数をList
などの具象クラスにしてしまうと、使う側はList
のインスタンスしか代入できなくなってしまいます。1このように、ポリモーフィズムによるメリットも享受することができます。
3. クラスへの安全なアクセスを提供することを目的としたインターフェイス
最後に、記事中盤でも解説した「クラスへの安全なアクセスを提供することを目的としたインターフェイス」です。
このインターフェイスの利点はもうお分かりいただけたと思うので、このタイプで有名なインターフェイスを紹介します。
インターフェイス 提供するアクセス IReadOnlyList
List
を読み取り専用で提供するIReadOnlyCollection
IReadOnlyList
から更にインデクサによるアクセスを削除IReadOnlyReactiveProperty
購読と値の読み取りだけができる ReactiveProperty
見ての通り、ただ単にアクセスを制限させるものばかりですね。
しかし、これが非常に重要な役割を持ちます。
List
をそのまま渡すのではなく、IReadOnlyList
として渡すだけで、渡した先で勝手に書き換えられる危険性が皆無2になりますから、これを使わない手はありません。
詳しくは下記記事で詳しく解説されているので、参考にしてください。自分も大変お世話になった記事です。
https://qiita.com/lobin-z0x50/items/248db6d0629c7abe47dd最後に
以上、「インターフェイス」の本質的な意味と、それからインターフェイスの3つのタイプを解説しました。
すごく長くなってしまいましたが、一口に「インターフェイス」と言っても、目的に応じて3つのタイプが有ることをご理解いただけたでしょうか。
インターフェイスの利点があまり良くわかっていない方も、「インターフェイスには3つのタイプがある」ことを意識するだけで、ぐっと理解度が深まると思います。ただ、冒頭でも書きましたがこの記事の内容は一個人の持論です。
もし説明がおかしい、間違っている等の他、ご意見ご感想などありましたら、ぜひぜひコメントください。すごく喜んで返信します。
もちろん、メソッド内で
List
の固有メソッドを使う場合は素直にList
で受けるべきです。 ↩[2020/09/10追記]@htsign さんにご指摘いただきました。
IReadOnlyList
として渡しても、IList
にキャストされてしまえば普通に追加削除可能なので、危険性が「皆無」というわけではないようです。ただ、IReadOnlyList
とあからさまに「リードオンリーなリスト」をわざわざ書き換え可能となるようにキャストするというのは、あまり考えられない行為と思いますので、IReadOnlyList
として渡す有用性は十分にあると感じます。もし本当に書き換えられるのを阻止したい場合、ImmutableList
もしくはReadOnlyCollection
を使えば実現可能です。 ↩