C#のデリゲート機能を使って関数言語っぽくコーディングできるか実験してみる
作るアプリは
「テキストボックスとラベルと計算ボタンがあって、計算ボタンを押すと何らかの計算をしてラベルに表示する」
というごく簡単なものです。
(更新履歴)
2016年8月13日
・計算ボタンを押した時のstackoverflowを解消した。ついでにエラー処理関数を登録して一気に実行するクラスを追加した。
・ソースを修正。ただし、このまま計算ボタンを押すとstackoverflowが発生するので、原因を調査の上、修正する。
FunctionTest.cs
using System;
using System.Collections.Generic;
using System.Windows;
namespace FunctionTest
{
//データ編集用の関数
delegate DataContext EditWindowData(DataContext context);
//入力チェック用の関数
delegate CheckResult Check(MyDisplay disp);
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
//クリアボタンを押したときの処理を記述します
private void On_click_Clear(object sender, RoutedEventArgs e) {
DataContext context = new DataContext();
bindingDataToDisplayFromModel(this, context, ClearLabelAndTextBox);
}
//計算ボタンを押したときの処理を記述します
private void On_click_Calc(object sender, RoutedEventArgs e)
{
//チェック用のコンテキストオブジェクトを生成する
DataContext context = new DataContext();
//View→Modelへのデータバインド
context = bindingDataToModelFromDisplay(this, context);
//入力チェックを行う
executeCheck(context);
//業務ロジックの実装(まだ実装していない)
//Model→Viewへのデータバインド
//EditDisplayValueAfuterCalcでモデル層に対する編集を行った後に
//データバインドを実施する。
bindingDataToDisplayFromModel(this, context, EditDisplayValueAfuterCalc);
//画面のGUIの状態を変化させる
changDisplay(context, this);
}
//Modelに対するチェックを行う処理を記述しています
private static DataContext executeCheck(DataContext context) {
context.CheckResultList.AddRange(
(new Checker())
.add(checkDateFormat) //日付チェックを行う
.call(context.Disp));
return context;
}
//日付チェックを行う
private static CheckResult checkDateFormat(MyDisplay disp) {
DateTime dateResult;
if (DateTime.TryParse(disp.Textbox, out dateResult))
{
return new CheckResult()
{ ErrorfiledName = "TextBox", Errortype = "DateFormat" };
}
return new CheckResult();
}
//Modelに対する操作を行う処理を記述しています。
//クリアボタンを押したときに画面にセットする値を編集しています。
private static DataContext ClearLabelAndTextBox(DataContext context)
{
MyDisplay disp = context.Disp;
disp.Label = "";
disp.Textbox = "";
context.Disp = disp;
return context;
}
//計算ボタンを押したときに画面にセットする値を編集しています。
private static DataContext EditDisplayValueAfuterCalc(DataContext context)
{
//編集は行わず、そのまま値を返却する。
return context;
}
//各層へのデータバインドを記述しています。
//View層→Model層へのデータバインド
private static DataContext bindingDataToModelFromDisplay(MainWindow win, DataContext context)
{
MyDisplay disp = new MyDisplay();
disp.Textbox = win.textBox.Text;
disp.Label = win.resultLabel.Content.ToString();
context.Disp = disp;
return context;
}
//Model層→View層へのデータバインド
private static void bindingDataToDisplayFromModel(MainWindow win, DataContext context, EditWindowData f) {
MyDisplay disp = f(context).Disp;
win.textBox.Text = disp.Textbox;
win.resultLabel.Content = (disp.Label);
}
//画面表示に関わる処理を行います
private static void changDisplay(DataContext context, MainWindow win)
{
//エラー発生時の処理
if (context.CheckResultList.Count != 0) { System.Windows.MessageBox.Show("エラー発生!"); }
}
}
//データ構造
//入力チェックを管理するクラス
class Checker{
private List<Check> _check = new List<Check>();
List<CheckResult> checkResultList = new List<CheckResult>();
public List<CheckResult> CheckResultList
{
get
{
return checkResultList;
}
}
//チェック処理を登録する
public Checker add(Check check) {
this._check.Add(check);
return this;
}
//登録したチェック処理の呼び出し
public List<CheckResult> call(MyDisplay disp) {
this._check.ForEach(f => { CheckResultList.Add(f(disp)); });
return checkResultList;
}
}
//関数間で持ちまわるデータを定義したクラス
public class DataContext
{
MyDisplay disp = new MyDisplay();
List<CheckResult> checkResultList = new List<CheckResult>();
public MyDisplay Disp
{
get
{
return disp;
}
set
{
disp = value;
}
}
public List<CheckResult> CheckResultList
{
get
{
return checkResultList;
}
set
{
checkResultList = value;
}
}
}
//入力チェックの結果を格納するクラス
public class CheckResult
{
string errorfiledName;
string errortype;
public string ErrorfiledName
{
get
{
return errorfiledName;
}
set
{
errorfiledName = value;
}
}
public string Errortype
{
get
{
return errortype;
}
set
{
errortype = value;
}
}
}
//画面のデータ構造を表すクラス
public class MyDisplay {
string textbox;
String label;
public string Textbox
{
get
{
return textbox;
}
set
{
textbox = value;
}
}
public String Label
{
get
{
return label;
}
set
{
label = value;
}
}
}
}
---MainWindow.xml
<Window x:Class="FunctionTest.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:FunctionTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="resetbutton" Content="リセット" HorizontalAlignment="Left" Height="40" Margin="70,225,0,0" VerticalAlignment="Top" Width="135" Click="On_click_Clear"/>
<Button x:Name="calc" Content="計算" HorizontalAlignment="Left" Height="40" Margin="235,225,0,0" VerticalAlignment="Top" Width="145" Click="On_click_Calc"/>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="40" Margin="80,30,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="180"/>
<Label x:Name="resultLabel" Content="" HorizontalAlignment="Left" Height="35" Margin="80,90,0,0" VerticalAlignment="Top" Width="240"/>
</Grid>
</Window>