看護業界からプログラミング分野に転向した学習者として、多くのプログラミング概念が医療業務の経験を通じて理解できることに気づきました。本記事では、病院情報システムの実際のシナリオに基づいて、C#のいくつかの重要な概念を詳しく解説します。
1. 名前空間
名前空間は、コードを整理・管理する方法であり、病院の組織構造のようなものです。たとえば病院は内科、外科、看護部など異なる部門に分かれており、各部門にはそれぞれの責任と管理範囲があります。C#において名前空間も同様の役割を果たし、次のことが可能です:
- 名前の衝突を避ける(病院の異なる科に同じ名前の看護師がいる場合と同様)
- 論理的なグループ化を提供する(病院の科の区分けのように)
- コードのアクセス範囲を制御する(病院の権限管理に類似)
1.1 名前空間の組織構造
namespace HospitalSystem // 病院システム全体
{
namespace Administration // 行政管理
{
// 人事管理、財務管理などのクラス
}
namespace Clinical // 臨床医療
{
namespace Internal // 内科
{
// 内科関連クラス
}
namespace Surgery // 外科
{
// 外科関連クラス
}
}
namespace Nursing // 看護部門
{
namespace WardManagement // 病棟管理
{
// 病棟関連のクラス
}
namespace MedicationManagement // 薬剤管理
{
// 薬剤関連のクラス
}
}
}
1.2 名前空間の使用方法
- クラスは名前空間に属すると考えることができる
- 現在のプロジェクトにクラスが属する名前空間がない場合、手動でそのクラスが存在する名前空間をインポートする必要がある:
- Visual Studio のヒントをクリックして使用
- ショートカットキー alt+shift+F10 を使用
- よく使う名前空間を覚えて手入力
1.3 プロジェクトでの名前空間の使用
// 方法1:using でインポート
using HospitalSystem.Nursing;
using HospitalSystem.Pharmacy;
// 方法2:完全修飾名を使用
HospitalSystem.Nursing.NursingRecord record = new HospitalSystem.Nursing.NursingRecord();
// 方法3:エイリアスを使用
using NurseRecord = HospitalSystem.Nursing.NursingRecord;
1.4 プロジェクト参照の例
あるプロジェクトで別のクラスを参照する場合:
- 参照を追加(例:HospitalCore.dll を追加)
- 名前空間を参照する
using HospitalCore.Models;
using HospitalCore.Services;
2. 値型と参照型
2.1 値型の詳細
値型はデータそのものを直接格納します。以下を含みます:
整数型
- sbyte:8 ビット符号付き整数 (-128 ~ 127)
- byte:8 ビット符号なし整数 (0 ~ 255)
- short:16 ビット符号付き整数
- ushort:16 ビット符号なし整数
- int:32 ビット符号付き整数(最もよく使われる)
- uint:32 ビット符号なし整数
- long:64 ビット符号付き整数
- ulong:64 ビット符号なし整数
浮動小数点型
- float:32 ビット単精度浮動小数点数(6 ~ 9 桁の精度)
- double:64 ビット倍精度浮動小数点数(15 ~ 17 桁の精度)
- decimal:128 ビット高精度小数(財務計算によく使われる)
その他の値型
- bool:ブール値 (true/false)
- char:16 ビット Unicode 文字
- enum:列挙型
- struct:構造体
医療システムでの応用例:
public struct VitalSigns
{
public int HeartRate; // 心拍数、通常60〜100の範囲
public decimal Temperature; // 体温、小数点第1位まで、例:36.5
public int BloodPressureHigh; // 収縮期血圧、例:120
public int BloodPressureLow; // 拡張期血圧、例:80
public bool IsFever; // 発熱の有無
// 列挙の例
public enum TemperatureMethod
{
Oral, // 口温
Axillary, // 腋窩温
Rectal // 直腸温
}
public TemperatureMethod Method { get; set; }
// 値型のデータ検証メソッド
public bool IsNormal()
{
return HeartRate >= 60 && HeartRate <= 100
&& Temperature >= 36.0m && Temperature <= 37.2m
&& BloodPressureHigh >= 90 && BloodPressureHigh <= 140
&& BloodPressureLow >= 60 && BloodPressureLow <= 90;
}
}
2.2 参照型の詳細
参照型には以下が含まれます:
クラス (class)
- すべてのカスタムクラス
- システム定義のクラス(String、Object など)
インターフェース (interface)
デリゲート (delegate)
配列 (array)
- 要素が値型でも参照型でも、配列自体は参照型
文字列 (string)
- よく使われるが、string は参照型
- 特殊な不変性を持つ
医療システムの例:
public class PatientRecord
{
// 基本情報
public string PatientName { get; set; }
public string IdNumber { get; set; }
public DateTime DateOfBirth { get; set; }
// 診断情報(配列の例)
public string[] Diagnoses { get; set; }
// 投薬情報(コレクションの例)
public List<Medication> Medications { get; set; }
// バイタルサイン記録(カスタムクラスの例)
public List<VitalSigns> VitalSignsHistory { get; set; }
// デリゲートの例 - バイタルサイン異常通知用
public delegate void VitalSignsAlertHandler(string message);
public event VitalSignsAlertHandler OnVitalSignsAlert;
// ディープコピーの例
public PatientRecord Clone()
{
var newRecord = new PatientRecord
{
PatientName = this.PatientName,
IdNumber = this.IdNumber,
DateOfBirth = this.DateOfBirth,
Diagnoses = (string[])this.Diagnoses.Clone(),
Medications = this.Medications.Select(m => m.Clone()).ToList(),
VitalSignsHistory = this.VitalSignsHistory.Select(vs => vs.Clone()).ToList()
};
return newRecord;
}
}
public class Medication
{
public string Name { get; set; }
public double Dosage { get; set; }
// ディープコピーメソッド
public Medication Clone()
{
return new Medication
{
Name = this.Name,
Dosage = this.Dosage
};
}
}
public class VitalSigns
{
public decimal Temperature { get; set; }
public int HeartRate { get; set; }
// ディープコピーメソッド
public VitalSigns Clone()
{
return new VitalSigns
{
Temperature = this.Temperature,
HeartRate = this.HeartRate
};
}
}
2.3 メモリ格納の違いの詳細
C#では、メモリはスタック (Stack) とヒープ (Heap) の2つの主要な領域に分かれています:
スタック (Stack)
- 値型のデータを格納
- システムがメモリの割り当てと解放を自動管理
- アクセス速度が速い
- スペースに限りがある
- 後入れ先出し (LIFO) の順序で格納
ヒープ (Heap)
- 参照型の実際のデータを格納
- ガベージコレクタ (GC) によるメモリ管理が必要
- 容量は大きいがアクセス速度は比較的遅い
- メモリ割り当てと解放がより柔軟
コード例:
public class MemoryExample
{
public void DemonstrateMemoryUsage()
{
// 値型の例
int temperature = 37; // スタック上に直接4バイト割り当て
bool isCritical = true; // スタック上に直接1バイト割り当て
DateTime checkTime = DateTime.Now; // DateTime は struct だが、スタック上に割り当て
// 参照型の例
string patientName = "山田太郎"; // ヒープ上に文字列データを割り当て、スタックに参照を格納
PatientRecord record = new PatientRecord(); // オブジェクトはヒープ上、参照はスタック上
// 値型のコピー例
int temp2 = temperature; // スタック上に新しい独立したコピーを作成
temp2 = 38; // temp2 を変更しても temperature には影響しない
Console.WriteLine($"元の温度: {temperature}, 新しい温度: {temp2}"); // 37, 38
// 参照型のコピー例
PatientRecord record2 = record; // 参照をコピー、両方の変数が同じヒープ上のオブジェクトを指す
record2.PatientName = "鈴木一郎"; // record2 を介した変更は record に影響する
Console.WriteLine($"recordの患者: {record.PatientName}"); // 出力: 鈴木一郎
// 文字列の特殊な例
string str1 = "テスト";
string str2 = "テスト"; // str2 と str1 は同じ文字列オブジェクトを指す(文字列プール)
string str3 = new string(new char[] { 'テ', 'ス', 'ト' }); // 強制的に新しいオブジェクトを作成
// 配列の例
int[] temperatures = new int[] { 36, 37, 38 }; // 配列オブジェクトはヒープ上、要素は配列オブジェクト内に連続して格納
int[] temps2 = temperatures; // 参照をコピー
temps2[0] = 39; // temps2 を変更すると temperatures にも影響
}
// 値型を引数として
public void UpdateTemperature(int temp)
{
temp = 39; // 元の値には影響しない
}
// 参照型を引数として
public void UpdatePatient(PatientRecord patient)
{
patient.PatientName = "田中花子"; // 元のオブジェクトに影響する
}
}
3. 文字列処理
文字列は医療情報システムで非常に頻繁に使用されます。例えば、カルテ記録や指示記録などです。C#では豊富な文字列処理メソッドが提供されています:
3.1 文字列の特性
- 不変性:文字列は不変であり、変更するたびに新しい文字列オブジェクトが作成される
- 文字列プール:同じ文字列リテラルは同一のオブジェクトを共有する
- char 型の読み取り専用配列と見なすことができる
3.2 よく使われる文字列メソッドの詳細
public class NursingNoteProcessor
{
public void ProcessNursingNotes()
{
// 看護記録の例
string note = " 患者:山田太郎、男性、62歳。\n" +
"警告:ペニシリンにアレルギーあり!\n" +
" 体温37.2℃、血圧120/80mmHg ";
// 1. 基本的な文字列操作
Console.WriteLine($"記録の長さ: {note.Length}"); // 文字列の長さを取得
// 2. 空白処理
string trimmed = note.Trim(); // 両端の空白を除去
string trimStart = note.TrimStart(); // 先頭の空白を除去
string trimEnd = note.TrimEnd(); // 末尾の空白を除去
// 3. 大文字小文字変換
string upper = note.ToUpper(); // 大文字に変換(重要な警告用)
string lower = note.ToLower(); // 小文字に変換
// 4. 検索操作
bool hasAllergy = note.Contains("アレルギー"); // 文字列が含まれているか確認
int allergyIndex = note.IndexOf("アレルギー"); // 最初の出現位置を検索
int lastIndex = note.LastIndexOf("、"); // 最後の出現位置を検索
// 5. 置換操作
string replaced = note.Replace("警告", "【警告】");
string noSpaces = note.Replace(" ", "");
// 6. 文字列分割
string[] lines = note.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
Console.WriteLine($"行内容: {line.Trim()}");
}
// 7. 部分文字列抽出
int ageStart = note.IndexOf("、") + 1;
int ageEnd = note.IndexOf("歳");
string age = note.Substring(ageStart, ageEnd - ageStart);
Console.WriteLine($"年齢: {age}"); // 出力: 62
// 8. 文字列比較
bool isEqual = note.Equals("その他の記録", StringComparison.OrdinalIgnoreCase); // 大文字小文字を区別しない比較
bool startsWith = note.StartsWith("患者"); // 先頭を確認
bool endsWith = note.EndsWith("mmHg"); // 末尾を確認
// 9. 文字列連結
string[] vitals = { "体温: 37.2℃", "血圧: 120/80mmHg", "心拍数: 75回/分" };
string summary = string.Join(", ", vitals);
Console.WriteLine($"バイタルサイン: {summary}");
// 10. 文字列ビルダー(大量の文字列連結を処理)
StringBuilder noteBuilder = new StringBuilder();
noteBuilder.AppendLine("患者基本情報:");
noteBuilder.AppendLine($"名前:山田太郎");
noteBuilder.AppendLine($"年齢:{age}歳");
noteBuilder.AppendLine($"バイタルサイン:{summary}");
string finalNote = noteBuilder.ToString();
}
}
4. 継承
継承はオブジェクト指向プログラミングの中核概念の一つであり、病院管理システムには多くの応用例があります。
4.1 継承の基本概念
継承の本質:
- コードの再利用
- クラス間の親子関係の確立
- ポリモーフィズムの実現
継承の特性:
- 単一継承:1つのクラスは直接の親クラスを1つしか持てない
- 伝達性:子クラスは親クラスのすべての特性を継承し、親クラスがその親から継承した特性も含む
- 子クラスは親クラスの機能を拡張できる
- 子クラスは親クラスのメソッドをオーバーライドできる
4.2 病院職員の継承体系の例
// 基底クラス:病院職員
public abstract class HospitalEmployee
{
protected string id;
protected string name;
public string Department { get; set; }
public DateTime HireDate { get; set; }
// コンストラクタ
public HospitalEmployee(string id, string name)
{
this.id = id;
this.name = name;
}
// 仮想メソッド - 子クラスでオーバーライド可能
public virtual string GetDutyDescription()
{
return $"{name}は{Department}で働いています";
}
// 抽象メソッド - 子クラスで必ず実装する必要がある
public abstract decimal CalculateSalary();
}
// 看護師クラス
public class Nurse : HospitalEmployee
{
public string NursingLevel { get; set; }
public string Shift { get; set; }
private decimal baseSalary;
public Nurse(string id, string name, string level)
: base(id, name)
{
NursingLevel = level;
SetBaseSalary();
}
private void SetBaseSalary()
{
switch (NursingLevel)
{
case "初級": baseSalary = 5000M; break;
case "中級": baseSalary = 7000M; break;
case "上級": baseSalary = 9000M; break;
default: baseSalary = 4000M; break;
}
}
public override string GetDutyDescription()
{
return $"{name}は{Department}の{NursingLevel}看護師で、{Shift}勤務です";
}
public override decimal CalculateSalary()
{
decimal shiftBonus = Shift == "夜勤" ? 1000M : 0M;
return baseSalary + shiftBonus;
}
}
// 主任看護師クラス
public class HeadNurse : Nurse
{
public List<Nurse> TeamMembers { get; set; }
private const decimal MANAGEMENT_BONUS = 2000M;
public HeadNurse(string id, string name)
: base(id, name, "主任")
{
TeamMembers = new List<Nurse>();
}
public void AddTeamMember(Nurse nurse)
{
TeamMembers.Add(nurse);
}
public override decimal CalculateSalary()
{
return base.CalculateSalary() + MANAGEMENT_BONUS;
}
// 新たな管理メソッド
public string GenerateTeamReport()
{
StringBuilder report = new StringBuilder();
report.AppendLine($"{Department}看護チーム報告");
report.AppendLine($"主任看護師:{name}");
report.AppendLine($"チームメンバー:{TeamMembers.Count}人");
foreach (var nurse in TeamMembers)
{
report.AppendLine($"- {nurse.GetDutyDescription()}");
}
return report.ToString();
}
}
5. コレクション
病院情報システムでは、患者リストや薬品在庫など、複数のデータ項目の集合を扱うことがよくあります。C#ではさまざまなコレクション型が提供されています。
5.1 ArrayList の詳細
ArrayList は非ジェネリックコレクションで、任意の型のオブジェクトを格納できます。
特徴:
- 動的なサイズ:自動的に容量を拡張
- 異なる型のデータを格納可能
- 型変換が必要で、パフォーマンスが低い
- 新規コードでは推奨されず、代わりにジェネリックの List
を推奨
public class PatientListManager
{
private ArrayList patientList = new ArrayList();
public void DemonstrateArrayList()
{
// 異なる型のデータを追加
patientList.Add("山田太郎"); // 文字列
patientList.Add(new Patient()); // 患者オブジェクト
patientList.Add(42); // 数値
// 容量と数
Console.WriteLine($"容量: {patientList.Capacity}"); // デフォルト4、必要に応じて倍に増加
Console.WriteLine($"実際の数: {patientList.Count}");
// 要素の挿入
patientList.Insert(0, "救急患者");
// 含まれているかの確認
bool hasPatient = patientList.Contains("山田太郎");
// 反復処理(型変換が必要)
foreach (object item in patientList)
{
if (item is Patient patient)
{
Console.WriteLine($"患者: {patient.Name}");
}
}
// 削除操作
patientList.Remove("山田太郎"); // 特定の要素を削除
patientList.RemoveAt(0); // 指定位置の要素を削除
patientList.Clear(); // コレクションをクリア
}
}
5.2 Hashtable と Dictionary の比較
キーと値のペアのコレクションは医療システムでよく使われます。例えば薬品在庫管理です。
Hashtable(非ジェネリック):
- キーと値は object 型
- 型変換が必要
- パフォーマンスが低い
- 新規コードでは推奨されない
Dictionary<TKey, TValue>(ジェネリック):
- 型安全
- パフォーマンスが優れている
- 推奨
public class MedicineInventoryManager
{
// Hashtableの例(旧方式)
private Hashtable medicineStockOld = new Hashtable();
// Dictionaryの例(推奨方式)
private Dictionary<string, int> medicineStock = new Dictionary<string, int>();
public void CompareCollections()
{
// Hashtable操作
medicineStockOld.Add("アモキシシリン", 100);
medicineStockOld["イブプロフェン"] = 50;
// 型変換の問題例
int oldStock = (int)medicineStockOld["アモキシシリン"]; // 明示的な変換が必要
// Dictionary操作
medicineStock.Add("アモキシシリン", 100);
medicineStock["イブプロフェン"] = 50;
// 安全な値の取得
if (medicineStock.TryGetValue("アモキシシリン", out int stock))
{
Console.WriteLine($"アモキシシリン在庫: {stock}");
}
// キーの存在確認
if (medicineStock.ContainsKey("イブプロフェン"))
{
medicineStock["イブプロフェン"] -= 10; // 在庫を減らす
}
// 反復処理の比較
foreach (DictionaryEntry entry in medicineStockOld)
{
Console.WriteLine($"薬品: {entry.Key}, 在庫: {entry.Value}");
}
foreach (KeyValuePair<string, int> kvp in medicineStock)
{
Console.WriteLine($"薬品: {kvp.Key}, 在庫: {kvp.Value}");
}
// キーまたは値のみの反復処理
foreach (string medicine in medicineStock.Keys)
{
Console.WriteLine($"薬品: {medicine}");
}
// リストに変換
List<string> medicineList = medicineStock.Keys.ToList();
}
}
5.3 List の詳細
List
public class NursingScheduleManager
{
private List<Nurse> nurses = new List<Nurse>();
public void DemonstrateList()
{
// 要素の追加
nurses.Add(new Nurse("N001", "佐藤看護師", "初級"));
nurses.Add(new Nurse("N002", "鈴木看護師", "中級"));
// 一括追加
var newNurses = new List<Nurse>
{
new Nurse("N003", "高橋看護師", "上級"),
new Nurse("N004", "田中看護師", "中級")
};
nurses.AddRange(newNurses);
// 検索操作
Nurse foundNurse = nurses.Find(n => n.NursingLevel == "中級");
List<Nurse> juniorNurses = nurses.FindAll(n => n.NursingLevel == "初級");
// 並び替え
nurses.Sort((n1, n2) => n1.NursingLevel.CompareTo(n2.NursingLevel));
// LINQ操作
var dayShiftNurses = nurses.Where(n => n.Shift == "日勤").ToList();
var nurseCount = nurses.Count(n => n.NursingLevel == "中級");
var orderedNurses = nurses.OrderBy(n => n.Name).ToList();
// 変換
var nurseNames = nurses.Select(n => n.Name).ToList();
// 条件の確認
bool hasNightNurse = nurses.Any(n => n.Shift == "夜勤");
bool allJunior = nurses.All(n => n.NursingLevel == "初級");
// 削除操作
nurses.Remove(foundNurse);
nurses.RemoveAll(n => n.NursingLevel == "実習");
// 範囲操作
var someNurses = nurses.GetRange(0, 2); // 最初の2人の看護師を取得
nurses.RemoveRange(0, 2); // 最初の2人の看護師を削除
}
}
6. 型変換の詳細
病院情報システムでは、型変換は異なる種類の医療従事者やカルテ記録を扱う際によく使われます。
6.1 型変換の基本概念
- 暗黙的な変換:自動的に行われ、安全でデータ損失なし
- 明示的な変換:手動で行う必要があり、データ損失のリスクがある
- 参照型の変換:継承関係のある型の変換
6.2 医療システムにおける型変換の例
public class StaffManager
{
public void DemonstrateTypeConversion()
{
// 基本型の変換
int heartRate = 75;
double hrDouble = heartRate; // 暗黙的な変換
decimal hrDecimal = (decimal)hrDouble; // 明示的な変換
// 文字列変換
string hrString = heartRate.ToString();
int parsedHR = int.Parse("75"); // 文字列から数値へ
// TryParseによる安全な変換
if (int.TryParse("75", out int result))
{
Console.WriteLine($"変換成功:{result}");
}
// 参照型の変換例
HospitalEmployee employee = new Nurse("N001", "佐藤看護師", "初級");
// 1. is演算子 - 型の確認
if (employee is Nurse)
{
Console.WriteLine("これは看護師です");
}
else if (employee is Doctor)
{
Console.WriteLine("これは医師です");
}
// 2. as演算子 - 安全な変換
Nurse nurse = employee as Nurse;
if (nurse != null)
{
Console.WriteLine($"看護師レベル: {nurse.NursingLevel}");
}
// 3. 明示的な変換
try
{
Nurse nurse2 = (Nurse)employee;
Console.WriteLine($"変換成功: {nurse2.NursingLevel}");
}
catch (InvalidCastException ex)
{
Console.WriteLine($"変換失敗: {ex.Message}");
}
// 4. パターンマッチング(C# 7.0以降)
if (employee is Nurse nurseMatch)
{
Console.WriteLine($"マッチ成功: {nurseMatch.NursingLevel}");
}
// 5. switchパターンマッチング
string GetEmployeeInfo(HospitalEmployee emp)
{
return emp switch
{
Nurse n => $"看護師 {n.Name}, レベル {n.NursingLevel}",
Doctor d => $"医師 {d.Name}, 専門 {d.Specialty}",
_ => $"職員 {emp.Name}"
};
}
}
// カスタム変換の例
public class VitalSignsConverter
{
public static implicit operator string(VitalSigns vs)
{
return $"体温: {vs.Temperature}℃, 心拍数: {vs.HeartRate}回/分";
}
public static explicit operator VitalSigns(string data)
{
// 簡易例、実際はもっと複雑な解析ロジックが必要
var parts = data.Split(',');
return new VitalSigns
{
Temperature = decimal.Parse(parts[0]),
HeartRate = int.Parse(parts[1])
};
}
}
}
6.3 型変換のベストプラクティス
安全な変換メソッドを優先:
- Parse より TryParse を使用
- 直接の型変換より as 演算子を使用
- 変換結果を常に確認
変換例外の処理:
public decimal ParseTemperature(string temp) { try { return decimal.Parse(temp); } catch (FormatException) { Console.WriteLine("体温の形式が正しくありません"); return 0; } catch (OverflowException) { Console.WriteLine("体温の値が範囲外です"); return 0; } }
## まとめ
この記事では、病院情報システムの実際のシナリオを通じて、C#のいくつかの重要な概念を詳しく解説しました:
1. **名前空間**
- 名前空間の組織構造を理解
- 名前空間の複数の使用方法を習得
- プロジェクト参照と名前空間のインポートを学習
2. **値型と参照型**
- 2つの型の本質的な違いを理解
- メモリ格納の異なる方式を習得
- 医療データを通じて型の特性を理解
3. **文字列処理**
- よく使われる文字列操作方法を習得
- 医療記録テキストの処理を学習
- 文字列の特殊な性質を理解
4. **継承**
- 病院職員体系を通じて継承の概念を理解
- 継承の特性と使用方法を習得
- メソッドオーバーライドとコンストラクタ呼び出しを学習
5. **コレクション**
- 異なるコレクション型の特徴を理解
- コレクションの一般的な操作を習得
- 適切なコレクション型の選択を学習
6. **型変換**
- さまざまな型変換方法を習得
- 安全な型変換の実践を学習
- 医療システムにおける型変換の応用を理解
これらの概念は、より複雑な医療情報システム機能を構築するのに役立ちます。