MVVM的に真っ当にMessageBoxを表示する
photo credit: waterj2 DSCF0629 via photopin (license)
MVVM的に正しくモーダルなメッセージボックスを表示するサンプルコードを示します。
WPFでのMVVMパターンとしては、ビュー以外からメッセージボックスを直接表示するのは良くないらしいです。
メッセージボックスで親ウィンドウを指定しないとモーダルにならないのですが「ビュー以外からメインウィンドウを逆方向へ参照するのは悪手」ということです。
じゃあ、どうすればよいかっていうと「メッセンジャーパターン」を使いましょうとのことでした。
「メッセンジャーパターン」て?
メッセンジャーパターンは「VMからViewを操作する方法」のことらしいです。 以下のようなカラクリだと理解しました。
- 「メッセンジャー」というオブジェクトからイベントを発行して、
- あらかじめビューに実装されたイベントトリガーが、このイベントを受けて、
- イベントトリガーが保有するトリガーアクションが処理をする(イベントの引数も渡される)。
上に書いた点を自分なりに整理して、ややこしいことを抜きで System.Windows.MessageBox と同じように使えるコードをご紹介します。 GitHub Gistにも置いていますのでご自由にご利用ください。
コードの概要
ここで、
- メッセンジャーは単なるクラス。
- イベントトリガーは、System.Windows.Interactibity.EventTriggerの派生クラス。
- トリガーアクションは、System.Windows.Interactibity.TriggerAction
の派生クラス。
※ System.Windows.Interactibityは、プロジェクトから参照されていないかもしれません。 参照マネージャの左のツリーから[アセンブリ]/[拡張]を開いてチェックを付ければOKです。
1.メッセンジャーとイベントトリガー(これが本体)
内部クラスを含めて3つのクラスを定義しています。System.Windows.MessageBoxと同じように使えるようにしたら長くなりました。
- MessageBox - メッセンジャー。スタティックなShowメソッドでシングルトンのインスタンスからイベントを発行。
- MessageBox.Action トリガーアクションの抽象基底クラス。実際にメッセージを表示するクラスの基本クラスです。
- MessageBoxTrigger - イベントトリガークラス。MessageBoxからのイベントを受け取って、トリガーアクションを実行します。
MessageBoxTrigger.cs
using System; using System.Linq; using System.Windows; using System.Windows.Interactivity; namespace StudyDotNet.Triggers { /// <summary> /// MVVM的メッセージボックスを表示するためのメッセンジャー。 /// /// MessageBox.Showメソッドで、イベントトリガーを起動する。 /// /// 実際の表示は、イベントトリガーから実行される /// トリガーアクションに実装される。 /// </summary> public class MessageBox { /// <summary> /// MessageBoxTriggerを起動するイベント。 /// </summary> public event EventHandler<EventArgs> ShowMessageBox; /// <summary> /// MessageBoxTriggerを起動するイベントの名前。 /// </summary> public static string EventName { get { return "ShowMessageBox"; } } /// <summary> /// このクラスはシングルトン。 /// </summary> public static MessageBox Instance { get; private set; } = new MessageBox(); private MessageBox() { } /// <summary> /// MessageBoxTriggerを起動するイベントの引数。 /// トリガーアクションへ渡されて処理される。 /// </summary> public class EventArgs : System.EventArgs { public string Text { get; set; } public string Title { get; set; } public MessageBoxButton Button { get; set; } public MessageBoxImage Icon { get; set; } /// <summary> /// メッセージボックスの結果を受け取るコールバック /// </summary> public Action<MessageBoxResult> NotifyResult { get; set; } } /// <summary> /// MVVM的メッセージボックスを表示。 /// 実際にはイベントを発行してイベントトリガーを起動する。 /// </summary> /// <param name="messageBoxText"></param> /// <param name="title"></param> /// <param name="button"></param> /// <param name="icon"></param> /// <returns></returns> public static MessageBoxResult Show( string messageBoxText, string title = null, MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.Information) { //メッセージボックスの結果 MessageBoxResult messageBoxResult = MessageBoxResult.Cancel; //イベントを発行する Instance.ShowMessageBox?.Invoke( Instance, new MessageBox.EventArgs() { Text = messageBoxText, Title = title, Button = button, Icon = icon, //コールバックで結果を受け取る NotifyResult = result => { messageBoxResult = result; } }); //メッセージボックスの結果を返す return messageBoxResult; } /// <summary> /// メッセージを表示するトリガーアクション実装用の抽象基底クラス。 /// /// 派生クラスでShowMessageを実装する。 /// </summary> public abstract class Action : TriggerAction<DependencyObject> { /// <summary> /// アクションの実態 /// </summary> /// <param name="parameter"></param> protected override void Invoke(object parameter) { //イベント引数の種別を検査 var messageBoxArgs = parameter as MessageBox.EventArgs; if(messageBoxArgs == null) { return; } //メッセージボックスの表示結果を取得 MessageBoxResult result = ShowMessage( messageBoxArgs.Text, messageBoxArgs.Title, messageBoxArgs.Button, messageBoxArgs.Icon); //コールバックで結果を通知 messageBoxArgs.NotifyResult?.Invoke(result); } /// <summary> /// メッセージボックスを表示する抽象メソッド。 /// </summary> /// <param name="text"></param> /// <param name="title"></param> /// <param name="button"></param> /// <param name="icon"></param> /// <returns></returns> abstract protected MessageBoxResult ShowMessage( string text, string title, MessageBoxButton button, MessageBoxImage icon); } } /// <summary> /// MVVM的メッセージボックスを表示するためのイベントトリガー。 /// /// MessageBox メッセンジャーの発行するイベントで起動される。 /// </summary> public class MessageBoxTrigger : System.Windows.Interactivity.EventTrigger { public MessageBoxTrigger() : base(MessageBox.EventName) { SourceObject = MessageBox.Instance; } } }
2.トリガーアクションの実装例
MesssageBoxのトリガーから実行されるトリガーアクションクラスの実装例です。
MessageBox.Actionから派生した、MessageBoxActionクラスを実装しています。 メッセージボックスを表示して、その結果を返すクラスです。
このクラスから使っている MessageBox クラスは、System.Windows のものということに注意してください。
MessageBoxAction.cs
using System.Windows; namespace StudyDotNet.Triggers { /// <summary> /// メッセージボックスを表示するトリガーアクション /// </summary> public class MessageBoxAction : MessageBox.Action { protected override MessageBoxResult ShowMessage( string text, string title, MessageBoxButton button, MessageBoxImage icon) { var owner = Application.Current.Windows .OfType<Window>().SingleOrDefault( x => x.IsActive); if(owner == null) { owner = Window.GetWindow(AssociatedObject); } return System.Windows.MessageBox.Show( owner, text, (title != null ? title : Application.Current.MainWindow.Title), button, icon); } } }
追記(2017-03-29): メッセージボックスのオーナーウィンドウに、アクティブウィンドウを指定するようにしました。メッセージボックスが出ている場合はnullになりうるのでその場合は従来通りのウィンドウとしています。(c# - Refer to active Window in WPF? - Stack Overflow)
3.メインウィンドウ(MainWindow.xaml)の記述例
上のメッセージトリガーとトリガーアクションクラスは以下のように、Xamlに Interaction.Trigger 要素を追加します。
XMLの名前空間、xmlns:i
と xmlns:tr
の宣言にも注意。i
は、System.Windows.Interactibityを使用するためで、
tr
は、上記のトリガー/アクションを使用するためのものです。
<Window x:Class="StudyDotNet.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:StudyDotNet" xmlns:vm="clr-namespace:StudyDotNet.ViewModels" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:tr="clr-namespace:StudyDotNet.Triggers" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <vm:MainWindowViewModel/> </Window.DataContext> <!-- ここから --> <i:Interaction.Triggers> <tr:MessageBoxTrigger> <tr:MessageBoxAction /> </tr:MessageBoxTrigger> </i:Interaction.Triggers> <!-- ここまで --> <Grid> <Button x:Name="button" Content="Button" Command="{Binding SampleCommand}" HorizontalAlignment="Left" Margin="50,50,0,0" VerticalAlignment="Top" Width="75"/> </Grid> </Window>
4.メッセージボックスを表示する
使う側からは単純に、MessageBox.Showを呼び出すだけです。 System.Windows.MessageBoxとゴッチャにならないように注意は必要。
using System; using System.Windows.Input; using StudyDotNet.Triggers; namespace StudyDotNet.Commands { public class SampleCommand : ICommand { public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { MessageBox.Show("MVVMバンザイ!"); } } }
いくつになってもお勉強
これによって、より少ない変更で、従来のメッセージボックス使用部分をメッセンジャーパターンに移行できます。
ユニットテストは、固定の応答を返すスタブで対応。
また、別の表示方法に切り替えるのも容易です。
例えば独自のウィンドウで表示したり、 ポップアップせずメインウィンドウ内にメッセージを配置したり、 履歴を参照する機能を追加したりと、広がりがあります。
ということで、やはりビュー以外からは、表示内容やUIに直接の関わりを持たないほうが良いのでしょう。
いくつになってもお勉強です。
WPFのMVVMでコマンドをバインディングする利点
photo credit: Storm Trooper at Oxford via photopin (license)
MVVMパターンでは、ボタンを押した時の処理などは、コマンドにバインディングしましょうということらしく、 従来の「Clickイベントをコードビハインドで受けて、、、」というのは嫌われるらしい。
しかし理由なく「MVVMでなきゃダメ!」と言われても納得しがたい。 「なぜ?」にはちゃんと答えて欲しいですよね。
ということで、この際きちんと理解しようと頑張りました。
いくつになってもお勉強です。
コマンドの実装例
具体的に、コマンドを利用するには
- 実行したい処理を、ICommandインターフェースを実装したコマンドクラスに記述して、
- ビューモデルから、そのインスタンスをプロパティで公開して、
- ボタンコントロールのCommand属性からバインディングする
と、これで幸せになれるらしい。
以降、以下の順に実装例を掲載してます。
- コマンドクラス:
SampleCommand.cs
- ビューモデル
MainWindowViewModel.cs
- ビュー:
MainWindow.xaml
- ビューのコードビハインド:
MainWindow.xaml.cs
(おまけ)
コマンド:SampleCommand.cs
押してから5秒間、押せなくなるボタンです。 やってることは無意味ですが、 最低限ICommandインターフェースの全機能を使おうとして長くなってしまいました。
using System; using System.ComponentModel; using System.Threading; using System.Windows.Input; namespace StudyDotNet.Commands { public class SampleCommand : ICommand { /// <summary> ///忙しいフラグ。忙しい時は何もできません。 /// </summary> private bool _isBusy = false; /// <summary> /// 忙しいフラグのプロパティ。 /// コマンドが実行可能かどうかに関連するプロパティなので。 /// 代入されたらCanExecuteChangedイベントを投げる。 /// </summary> public bool IsBusy { get { return _isBusy; } set { _isBusy = value; CanExecuteChanged?.Invoke(this, new EventArgs()); } } /// <summary> /// 以下でタイマー使っているので、 /// UIスレッドで画面更新するために必要 /// </summary> private AsyncOperation _uiThreadOperation = AsyncOperationManager.CreateOperation(null); // // 以降 ICommand インターフェースの実装 // public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { //忙しくない時だけコマンド実行できる Console.WriteLine("実行可否を調べられてる。" + (!IsBusy?"お仕事できます":"今無理です")); return !IsBusy; } public void Execute(object parameter) { //忙しいフラグON //一定時間後には暇になる。 Console.WriteLine("忙しくなるぞー"); IsBusy = true; Timer _busyTimer = new Timer( timerParam => { _uiThreadOperation.Post(updatePropParam => { IsBusy = false; Console.WriteLine("暇になった。"); }, null); }, null, 5000, Timeout.Infinite); } } }
CanExecuteChanged
イベント - コマンドを実行可否の状態変化時に発行するイベント。UIオブジェクトではこのイベントによって、画面表示状態を変更する。なので別スレッドから投げるときは、AsyncOperationでUIスレッドへActionをPostしなくてはなりませんね。CanExecute
プロパティ - コマンドが実行可能な状況ではtrue、実行できない状況ならfalseを返すプロパティです。Execute
メソッド - コマンドの処理本体です。
ビューモデル MainWindowViewModel.cs
ビューモデルは非常にシンプル。上のコマンドクラスのインスタンスをパブリックプロパティとして持ってるだけ。 「この画面にはモデルを操作するための、こういう名前のコマンドがありますよ」と。そして「何をするかはコマンドを見て頂戴」というところでしょうか。
using StudyDotNet.Commands; namespace StudyDotNet.ViewModels { class MainWindowViewModel { public SampleCommand SampleCommand { get; private set; } = new SampleCommand(); } }
このプロパティのsetterからは、INotifyPropertyChanged
インターフェースを実装して、PropertyChanged
イベントを発行すべきだと思い込んでいましたが、なくても動いているようです(ここ、ちょっと理解が曖昧)。
ビュー:MainWindow.xaml
ビューからは ButtonのCommand属性からビューモデルのコマンドにバインディング。
Button
のCommand="{Binding SampleCommand}"
の部分ですね。
<Window x:Class="StudyDotNet.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:StudyDotNet" xmlns:vm="clr-namespace:StudyDotNet.ViewModels" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <vm:MainWindowViewModel/> </Window.DataContext> <Grid> <Button x:Name="button" Content="Button" Command="{Binding SampleCommand}" HorizontalAlignment="Left" Margin="50,50,0,0" VerticalAlignment="Top" Width="75"/> </Grid> </Window>
ビューのコードビハインド:MainWindow.xaml.cs
(おまけ)
ちなみにMainWindowのコードビハインドは、何も触っていません。
using System.Windows; namespace StudyDotNet { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } }
コマンドを使う利点
処理をコマンドクラスに記述することで得られる利点は、以下のようなものかと推測します。他にもたくさんありそな気もする。
- コードビハインド(
*.xaml.cs
)に何も書く必要がない。 - コマンドの単体テストで、ビューやビューモデルが不要です。
一言でいえば、コマンドの独立性が保たれて、ビューモデルがシンプルに保てます。疎結合はいつでも正義。
コマンドの実行可否がコマンド自体から得られます(CanExecute
プロパティ)し、UIオブジェクトに直結しているので、別途IsEnabled
プロパティでバインディングしなくても良いのです。複数の条件が絡み合ったUIオブジェクトが複数ある場合、ビューモデルにIs〇〇ButtonEnabled
みたいなプロパティがゴロゴロ沸いてきて煩雑になりやすいですからね。
コマンド処理のアンチパターン
上に書いたように、コマンドには明らかなメリットがありますが、一発で台無しにする方法があります。
それは、コマンドの処理内で昔ながらのメッセージボックスやダイアログを直接モーダル表示することです。
お手軽なので、ついついエラーの表示や問い合わせ等で使いがちですが、よろしくない。
なぜかというとユニットテストが自動ではなくなってしまいます。例えばユーザーがOKボタンを押さないとテストが進まないという事態になります。あとモーダルダイアログとして表示するためにはメインウィンドウが必要なのですが、コマンドの処理の中でメインウィンドウを参照するのもコードの独立性が破れて嫌われます。
コマンド処理内でユーザー入力が必要な場合は、MVVMでの「メッセンジャーパターン」という機構で回避できます。 ユニットテストでは簡単に応答を返すスタブに差し替えられます。 この「メッセンジャーパターン」については、以下のページに詳しく書いていますので是非。
いくつになってもお勉強
設計思想的には古くからあるDocument-ViewやMVCなどを最新技術で発展させたもののようですね。
古くはVC++のMFCアプリケーションでメニュー項目などを更新するUpdateUIなどと同じ便利さ加減と思います。便利なんだけどひと手間必要というのも同じですね。当時、あの機構をまともに理解している人が少なくて、往生した覚えがありますわって話が古くて伝わってない?
なにより、「どんな利点があるのか」という観点から、腰を据えて理解できて非常によかった。
フタを開ければかつて知ったる設計思想的な気分でもありました。
やっぱり、いくつになってもお勉強ですねー。
関連記事:
C#のラムダ式はAction・Funcと一緒に理解する
photo credit: Imperial Shuttle via photopin (license)
何事も表面的な丸暗記でなく、基礎からキッチリ理解するほうが結果的には早道ですね。
C#のラムダ式に関しては、ActionクラスととFuncクラスをしっかり理解しておけば、それほど難しくはないはずです。
C#のActionクラスは、戻り値のないメソッドを表すクラスで、Funcは戻り値のあるメソッドです。
どちらもジェネリッククラスで、Actionは引数リスト、Funcは戻り値の型と引数リストをジェネリックパラメータで指定します。
Actionはメソッドの動作内容に注目した名前であり、Funcは数学的な関数として評価値を持つ(=戻り値がある)ということですね。
この2つのクラスについては以下のページで詳しく書いていますので参照してください。
C#のラムダ式
ラムダ式の記法的には、以下のような感じ。引数がひとつなら丸かっこは不要とか、波かっこの中身が単一の文なら(複文でないなら)波かっこは要らないとか、いろいろあるんだけど、基本はこちらでOKです。
(name,age) => { Console.WriteLine( string.Format( "{0} is {1} years old", name, age)); };
型とか指定されていないし、戻り値ってありなの?無しなの?どうでもいいの?てな具合に、全く情緒が安定しない代物ですが・・・
ラムダ式は即時呼び出しできません
JavaScript的にラムダ式を直接呼び出そうとしてエラーになって「何がダメなの?」と混乱しました。 「C#のラムダ式ってJavaScriptの無名関数と一緒でしょ?関数オブジェクトそのものでしょ?」的な思い込みがあったんですね。
でもそれは間違い。C#のラムダ式は、それそのものを呼び出すことができません。
ラムダ式というランタイムオブジェクトは無い
C#のラムダ式は、ActionクラスかFuncクラスのインスタンスを生成するためのものなんですよね。
そもそもこれまで、ラムダ式を見たとき「型が明示されていないのに、どうコンパイルされて実行されているのだ?」と思っていたのですが、正しく動いているコードなら、一見欠落しているように見える引数や戻り値の型情報は、一緒に使われている Action
や Func
、またはデリゲートで推測可能になってるはず。
ラムダ式はそれらのオブジェクトを生成するために使われるのですが、ランタイムに「ラムダ式」というオブジェクトとして存在しているわけではないということです。
単なる記法、シンタックス・シュガーです
つまり「ラムダ式は、デリゲートやActionやFuncを記述するためのシンタックス・シュガー」であって、それ自体はオブジェクトでもなんでもなく「単なる記法」というわけです。
その証拠に、JavaScriptの即時関数(以下)のようなコードは、C#のラムダ式では実装不可です。コンパイルが通りません。
//JavaScriptの即時関数呼び出し var a = 1; (function(b) { a += b; }(2)); console.log("a:", a);// "a: 3"
C#では、一旦Action
のインスタンスを作ってからでないと実行できません。
なぜかというと、ラムダ式だけでは、引数の型が特定できないからなんですね。
以下のコードでは、Action
クラスによって、第一引数がint
であることが明示されている(あえて似せて書いていますので無駄に丸かっこが付いています)ので実行可能となるわけです。
int a = 1; (new Action<int>(b => { a += b; })(2)); Console.WriteLine(string.Format("a: {0}", a));
JavaScriptでの無名関数は関数オブジェクトですが、C#のラムダ式に直接対応するオブジェクトはないってことです。 ちなみにJavaのラムダ式は無名クラスのインスタンスなんですね。 言語によって扱いが異なるのは興味深いがヤヤコシイ。
セルの範囲に入力された最も右の値を得るには「MATCH」を使う
photo credit: Bailey holding a mug via photopin (license)
Crystal
新品 ¥5,499 0個の評価
Amazon.co.jpで詳細を見る
セルの範囲内で、最も右の入力された(空白でない)値を得る には、ワークシート関数 MATCH が使えるんですね(INDEXとMAXも使います)。
かれこれ20年以上エクセルを使い続けていますが、今になって新たなワークシート関数を知って感心するとは思ってもいませんでした。
長年VBAのマクロでやっていましたが計算式だとお手軽です。
そして、このワークシート関数が使えるのはエクセルだけではありません。Google Drive のスプレッドシートでも使えます。
以降で実際の Google スプレッドシートを共有していますので、ご利用ください。
関連記事はこちら↓になります:
takamints.hatenablog.jp
事の発端は進捗管理
先日、個人作業の進捗管理のシートを軽い気持ちで作っていたんですね。
縦方向に作業項目を並べて、横方向には当日から締め切りまでの日付が並ぶ。各項目の進捗状況を日毎に記録するものです。
期限内に全作業がきちんと終了するよう自己管理。日々の状況を把握して、対策打ったり、あきらめたり(笑)、ふさぎ込んだり(!?)ってな使い方です。
客観的マネージメントには数値管理が必要ですし、なにより残工数とか表示しちゃって、予測曲線プロットしたりと、もうデータフェチにはたまらない喜びなわけですよ。理解できないかもしれないけど。
一番右が欲しいのです
作業項目は、1人日(いちにんにち = 一人の人が一日でできる作業)程度に分割しておき、各項目行の日毎のセルに0.5
=「今やってます」とか、1.0
=「完了した」とか書いていく。
でも、完了したり、そのあと進捗に変化がない日には、同じ数値を入れたくない。手間だし、表としても見にくいし。
なので、各項目の最新の進捗率は、各行の中で、数値が入力されているセルのうち、一番右にあるセルの値を取り出したい。
過去にはVBAのマクロでやってたんですよ。 でも入力時のトリガーで再計算とか結構遅くなりますし、別のシートへ展開しにくいんですよね。 データ管理用のシートに分けたりしてみましたが、行や列の挿入で簡単に破たんしてしまいます。 この辺はホント「エクセルあるある」だと思います。
なので、できれば計算式でやりたいが、そのやり方が分からなかったのです。
過去の自分にググレカス
で、この度ググってみたら以下のサイトが見つかった。
EXCELで、範囲指定した一番右の数値(セル)を返す関数ってありますか?...-Yahoo!知恵袋
detail.chiebukuro.yahoo.co.jp
というか他にもゴロゴロ出てきますやん。しかもそれぞれ、結構古くて2009年とか、7年前の情報です。
なんで今まで検索しなかった?と不思議でしたが、よくよく思い出してみると、ちょうどワタシが管理業務から逃れた離れることになった時期。
当時、このような管理面のことを、(一時的に)ちっとも考えなくなっていたんだな。ダメですね。
MATCHで~す
売り上げランキング: 1,343
売り上げランキング: 26,668
まあ、とにかく、そこで紹介されているのは、以下のような計算式でした。
セル範囲A1:E1
に入力されてる最も右の値を得る:
= INDEX ( A1:E1, MATCH( MAX(A1:E1) + 1, A1:E1, 1 ))
INDEX関数
INDEX関数は、セル範囲からインデックスを指定して値を得る関数です。
第一引数がセルの範囲で、第二引数がインデックス。上の式ではA1:E1
がセル範囲。MATCH( ... )
がインデックスですね。
MATCH関数
で、INDEXの第二引数に指定されてるMATCH関数は、一次元のセル範囲(つまり1行か1列)から検索値にマッチする値が入力されたセルの位置(インデックス)を返す関数です。第一引数が検索値、第二引数がセルの範囲、第三引数が検索の型。
MAX関数
その名の通り、範囲内から最大値を得る関数ですね。
ちょっと待って、なんで最大値を探すのだ?
しかしちょっと待ってくださいよ、上の例では、検索値が、MAX(A1:E1)+1
となっていて、セル範囲内の最大値+1となっているではないですか。これ検索してもダメなんじゃ?!と思ったのですが、どうやらミソは第三引数の検索の型。検索の型が1
だと、検索値以下の最大値にヒットするのだとか。(だから本来+1する必要はないのだと思います)。しかも省略時のデフォルトが1
なので省略してもよいみたい。
まだワカラン。一番右が最大だとは限らんのでは?
しかしまだ、納得できない。釈然としない。最大値の位置を知っても、それが一番右にあるとは限らないのだから、これでは目的の値は得られないのでは?と疑いましたが、なんとMATCH関数の説明に、セル範囲内のデータは昇順にソートされている前提と書かれているんですね。
エクセルのヘルプでは、たまにこういった軽く意味不明の記述がありまして、見るたびに「なんちゅう都合のいい仕様?!www」と嘲笑とか憤慨を覚えていました。
しかし、どうやら、この前提のおかげで、最後まで検索してくれるよう。こういう目的で使いたいから、こんな妙な前提を入れているのかなとか思ったりもしたがはて?。
サンプル(Googleスプレッドシート)
最近気づいたVisualStudio 2015 C# で便利に使える5つの機能
全国的に梅雨も明け、本格的な夏ですね。 自分的には(仕事で)年に一度の恒例のVisualStudioシーズン・イン。 昨年までは冬場が多く、期間は長くても2か月程度。 しかし今年は 6月初めから徐々に動き出して、7月以降に本格化。 期間はトータル4、5か月になりそうです。 終わるころには秋ですね。
例年そんなインターバルでやっているので、VisualStudioやC#の、最新開発状況(環境やプログラミングスタイルなど)になかなか追従できていないのですが、今回は多少期間が長いため、視野がちょっとだけ広くなったか「あ、こんなことができるんだあ」とか「こんな風に書けるのねー」みたいなことが何度かありましたので書いておきます。
あくまでも、自分が知らなくて最近気が付いたってことですので、皆さんご存知なことばかりかも。 しかし、いくつになってもお勉強。新知識には興奮します。
- プロパティ名を指定せずOnPropertyChangedを呼ぶ
- nullチェックの簡略記法
- 自動実装プロパティの初期値設定
- 読み出し専用プロパティを自動実装
- プロパティやメソッドのスニペットを挿入する
売り上げランキング: 42
1. プロパティ名を指定せずOnPropertyChangedを呼ぶ
INotifyPropertyChanged
を実装したクラスのプロパティに値を設定されたとき、PropertyChangedイベントを発生させますが、これまで以下のようにプロパティ名を文字列で指定して、OnPropertyChanged
メソッドを呼び出していました。律儀にね。
using System.ComponentModel; namespace Application { class ViewModel: INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public virtual void OnPropertyChanged(string name) { if(PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name)); } } private int _propFoo; public int PropFoo { get { return _propFoo; } set { _propFoo = value; OnPropertyChanged("PropFoo"); } } } }
ところが、プロパティが増えると、そのうち必ず間違えるでしょう?それに、名前を変えたら文字列も変えなきゃならないわけで。 多分おそらく、やってられないですよコレは。
そこで、
[CallerMemberName]
を使いましょう
以下のように、OnPropertyChanged
メソッドの、プロパティ名称を受け取る文字列引数を、省略可能(既定値は空文字列)にして、[System.Runtime.CompilerServices.CallerMemberName]
属性を設定しておくと、引数を省略して呼び出しても、呼び出し元のプロパティ名(以下の例では"PropFoo"
)が自動的に渡されるのです。
using System.ComponentModel; namespace Application { class ViewModel: INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; // CallerMemberNameアトリビュートを名前を受け取る文字列引数に指定する。 public virtual void OnPropertyChanged( [System.Runtime.CompilerServices.CallerMemberName] string name = "") { if(PropertyChanged != null) { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name)); } } private int _propFoo; public int PropFoo { get { return _propFoo; } set { _propFoo = value; //引数を省略して呼び出すと、このプロパティ名"PropFoo"が渡される OnPropertyChanged(); } } } }
これなら、リファクタリングし放題ですねっ。
※ 上の例では、あえて冗長に属性名をフルパスで書いていますが、実際の局面では、いきなり[CallerMemberName]
と書いてから(この時点ではコンパイルエラーかも)、[Ctrl]+[.]
で、using System.Runtime.CompilerServices
を追加すれば良いですよ。
つまり呼び出し元のメンバ名が渡される
ここの例では、プロパティから呼び出しているのでプロパティ名になっていますが、メソッドから呼び出せば、そのメソッド名が渡されます。 CallerMemberNameの名のとおり、呼び出した側のメンバ名が渡される。
本来イベントとは何の関係もない機能ですが、気付くきっかけが「OnPropertyChangedじゃまくせー」と思って調べていた時だったのでこうなった。 デバッグログを出力するような場合にも有用でしょうね。
2. nullチェックの簡略記法
if文でオブジェクトがnullでないことを確認してから、そのメソッドを呼び出す処理は、さらにシンプルに書けます。 一つ上の例では、イベントのリスナーがいるかどうかを確認しています(以下に抜き出してます)
public event PropertyChangedEventHandler PropertyChanged; public virtual void OnPropertyChanged(string name) { if(PropertyChanged != null) {//←ここ PropertyChanged.Invoke(this, new PropertyChangedEventArgs(name)); } }
長年「こういうものだ」と思っていたので特に不便さは感じていませんでしたが、最近のC#では、以下のように?
を使って短く書ける。知ってしまうともう戻れない。
public event PropertyChangedEventHandler PropertyChanged; public virtual void OnPropertyChanged(string name) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
対象のオブジェクト(上の例ではPropertyChanged
)がnullでないならそのままメソッドを呼び出しますが、nullなら何も行わない。
最初、「え?」と思いました。そういえばnull許容型で似た記述をしますが、三項演算子の発展形(省略形?)のようにも思えます。
これは、メソッド呼び出しのための構文ではなく対象オブジェクトがnullかどうかによって、その後のメンバーの参照をするかどうかということです。 そして、以下のようにチェーンできますから、オブジェクトの階層が深い場合は、かなり有利。途中のプロパティやメソッドの戻り値がnullなら、nullとして評価され、それ以降は評価されないということですね(多分)。
Foo?.Bar?.Baz()?.Hoge("Fuga");
メッチャ強力。過去のコードを修正したくなりますね(しないけど)。いやしかし、こりゃ楽でいい。
3. 自動実装プロパティの初期値設定
地味ではあるけど、これも由。
自動実装プロパティに特定の初期値を与えるためには、以下のように、コンストラクタで値を設定しなくてはならなかったと思っていましたが、
//従来の自動実装プロパティを持つクラス class Foo { public int Bar { get; set; } public Foo() { Bar = 999; } }
プロパティ宣言部分で値を設定できる(以下)ようになっていました。
//最新式(笑)の自動実装プロパティを持つクラス class Foo { public int Bar { get; set; } = 999; }
これも楽です便利です。
自動実装で楽しているにもかかわらず、コンストラクタを別途定義するとかアホちゃうかと潜在的に思っていましたが、やっと報われた(謎)。
4. 読み出し専用プロパティを自動実装
売り上げランキング: 79,718
もうひとつ自動実装プロパティネタ。
GetterがパブリックでSetterはプライベートというプロパティは自動実装できないものだと思い込んでいて、常々以下のように書いていました。
class Foo { private int _bar = 999; public int Bar { get { return _bar;} } }
でも、以下のように書けるのだとか。
class Foo { public int Bar { get; private set; } = 999; }
これは、自分が知らなかっただけかな。
5. 自動実装プロパティのスニペットを挿入する
プロパティを新設するとき、全部自分でキー入力していましたが、エディタでprop
と入力して、[Tab]
を2回叩けば、とりあえずint型のMyPropertyという自動実装プロパティが挿入されますね。
ほかにも便利なスニペットがあるかもしれん。いろいろ探し歩いてみます。
売り上げランキング: 4,385