この記事は
C# Advent Calendar 2025 23日目の記事です。
※本記事は、本文精査にChatGPTを使用しております。
はじめに
業務でWinFormsを使っているとFormが重くなってくると思います。重くなってきてからMVPなどのアーキテクチャ思想にそってリファクタするのも大変ですよね。5000~6000行のクラスをリファクをした時は絶望しました。無理だと・・・。そこで、今回は比較的簡単にできる肥大化したFormを簡単にリファクタを2選紹介します。
リファクタ2選 + 番外編
WinFormsでは、Formがロジックを持ちすぎると破綻します。解決策はシンプルで、Form以外のクラスを作ることです。
1.ロジックを「Service」に移動
Formにロジックを書きだすのはあるあるだと思います。しかし、ロジックを書き出すとFormが数千行と肥大化するのでServiceに移動させましょう。全てのロジックを移動させると今度はServiceが肥大化します。それについては番外編で紹介します。
Before
public partial class UserForm : Form
{
private void btnRegister_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtName.Text))
{
MessageBox.Show("名前を入力してください");
return;
}
if (!int.TryParse(txtAge.Text, out var age))
{
MessageBox.Show("年齢は数値で入力してください");
return;
}
if (age < 0 || age > 120)
{
MessageBox.Show("年齢が不正です");
return;
}
if(userRepository.FindPhoneNumber(phoneNumber.Text)){
MessageBox.Show("既に登録されている電話番号です");
}
MessageBox.Show("登録しました");
}
}
After
public partial class UserForm : Form
{
private UserService _service;
public UserForm(){
__service = new UserService();
}
private void btnRegister_Click(object sender, EventArgs e)
{
var input = new UserInput
{
Name = txtName.Text,
AgeText = txtAge.Text
};
var result = _service.Validate(input);
if (!result.IsValid)
{
MessageBox.Show(result.ErrorMessage);
return;
}
MessageBox.Show("登録しました");
}
}
2.画面のViewロジックを「Helper」に逃がす
画面の更新に関するロジックをHelperに移動させます。
ここでは簡単にFormを軽くするためにリファクタ戦略として、MVPのPresenterほどのことは行わないというのがポイントです。
Before
public partial class OrderSearchForm : Form
{
private void txtOrderNo_TextChanged(object sender, EventArgs e)
{
UpdateSearchState();
}
private void dtpFrom_ValueChanged(object sender, EventArgs e)
{
UpdateSearchState();
}
private void dtpTo_ValueChanged(object sender, EventArgs e)
{
UpdateSearchState();
}
private void UpdateSearchState()
{
btnSearch.Enabled = false;
lblMessage.Text = "";
// 注文番号 or 日付範囲のどちらか必須
if (!string.IsNullOrWhiteSpace(txtOrderNo.Text))
{
btnSearch.Enabled = true;
return;
}
if (dtpFrom.Value > dtpTo.Value)
{
lblMessage.Text = "開始日は終了日以前を指定してください";
return;
}
btnSearch.Enabled = true;
}
}
After
public partial class OrderSearchForm : Form
{
private OrderSearchFormHelper _helper;
public OrderSearchForm()
{
InitializeComponent();
_helper = new OrderSearchFormHelper();
}
private void txtOrderNo_TextChanged(object sender, EventArgs e)
{
UpdateSearchState();
}
private void dtpFrom_ValueChanged(object sender, EventArgs e)
{
UpdateSearchState();
}
private void dtpTo_ValueChanged(object sender, EventArgs e)
{
UpdateSearchState();
}
private void UpdateSearchState(){
var state = _helper.DecideSearchState(
txtOrderNo.Text,
dtpFrom.Value,
dtpTo.Value
);
btnSearch.Enabled = state.CanSearch;
lblMessage.Text = state.Message;
}
}
public class OrderSearchFormHelper
{
public SearchState DecideSearchState(
string orderNo,
DateTime fromDate,
DateTime toDate)
{
if (!string.IsNullOrWhiteSpace(orderNo))
{
return SearchState.Can();
}
if (fromDate > toDate)
{
return SearchState.Cannot("開始日は終了日以前を指定してください");
}
return SearchState.Can();
}
}
public class SearchState
{
public bool CanSearch { get; }
public string Message { get; }
private SearchState(bool canSearch, string message)
{
CanSearch = canSearch;
Message = message;
}
public static SearchState Can()
=> new SearchState(true, string.Empty);
public static SearchState Cannot(string message)
=> new SearchState(false, message);
}
番外編
- 一つのServiceに詰め込みすぎない。FormとServiceが1対1である必要はありません。
- 入力チェックはServiceからValidatorに移動させる。
おわりに
WinFormsがViewとロジックが近いため、Formが肥大化しがちだと思います。さらに、業務システムだと現場の変化に応じて業務ロジックが複雑になったり、コードも肥大化しがちだと思います。今回、紹介した手法で少しでも肥大化を解消する第一歩になればいいなと思います。