## はじめに
C#のDelegateは、メソッドを変数のように扱える強力な機能です。適切に活用することで、コードの柔軟性と再利用性が大きく向上し、保守性の高いアプリケーションを実現できます。
本記事では、Delegateの基礎から実践的な活用方法まで、具体的なコード例と共に詳しく解説します。
Delegateの基本的な概念と動作の仕組み
実践的なDelegate活用パターンと実装方法
パフォーマンスを考慮したDelegateの最適化手法
メモリリークを防ぐためのベストプラクティス
マルチスレッド環境での安全な使用方法
実際のプロジェクトで使える具体的な実装例
プラグイン機構やイベント処理などの実践的な設計パターン
C# Delegateとは?基礎から完全理解
デリゲート(Delegate)は、C#におけるメソッドの参照型です。簡単に言えば、メソッドを変数のように扱えるようにする機能です。
これにより、メソッドを引数として渡したり、実行時に動的にメソッドを切り替えたりすることが可能になります。
1. コードの疎結合化
課題: コンポーネント間の強い依存関係により、コードの変更が困難
解決方法: デリゲートを使用してコールバック機能を実装し、コンポーネント間の直接的な依存を避ける
メリット: 保守性の向上、テスト容易性の向上
// 従来の実装(強結合)
public class OrderProcessor {
private EmailNotifier _notifier = new EmailNotifier();
public void ProcessOrder(Order order) {
// 注文処理
_notifier.SendNotification(order); // 直接依存
}
}
// デリゲートを使用した実装(疎結合)
public class OrderProcessor {
private Action<Order> _notificationDelegate;
public OrderProcessor(Action<Order> notificationMethod) {
_notificationDelegate = notificationMethod;
}
public void ProcessOrder(Order order) {
// 注文処理
_notificationDelegate?.Invoke(order); // デリゲート経由で通知
}
}
2. 実行時の振る舞い変更
課題: アプリケーションの動作を動的に変更する必要性
解決方法: デリゲートを使用して実行時にメソッドを切り替える
メリット: 柔軟性の向上、拡張性の確保
// 計算処理をデリゲートで切り替え
public class Calculator {
private Func<double, double, double> _operation;
public void SetOperation(Func<double, double, double> operation) {
_operation = operation;
}
public double Calculate(double x, double y) {
return _operation(x, y);
}
}
// 使用例
var calc = new Calculator();
calc.SetOperation((x, y) => x + y); // 加算
var result1 = calc.Calculate(5, 3); // 結果: 8
calc.SetOperation((x, y) => x * y); // 乗算に切り替え
var result2 = calc.Calculate(5, 3); // 結果: 15
3. イベント処理の実装
課題: 複数のコンポーネント間でのイベント通知の実装
解決方法: デリゲートベースのイベント機能を使用
メリット: 疎結合なイベント処理の実現、コードの整理
// イベント処理の実装例
public class StockMonitor {
// デリゲートの定義
public delegate void StockPriceChangedHandler(decimal oldPrice, decimal newPrice);
// イベントの宣言
public event StockPriceChangedHandler PriceChanged;
private decimal _currentPrice;
public decimal CurrentPrice {
get => _currentPrice;
set {
if (_currentPrice != value) {
decimal oldPrice = _currentPrice;
_currentPrice = value;
// イベント通知
PriceChanged?.Invoke(oldPrice, value);
}
}
}
}
Delegateの動作の仕組み
デリゲートは内部的に以下のような仕組みで動作します。
1. デリゲートインスタンスの生成
- デリゲートは実際にはクラスのインスタンスとして生成される
- メソッド情報とターゲットオブジェクトの参照を保持
2. メソッド呼び出しの仕組み
// デリゲートの宣言 public delegate int CalculateDelegate(int x, int y); // メソッドの定義 public static int Add(int x, int y) => x + y; // デリゲートの使用例 CalculateDelegate calc = Add; // メソッド参照の保持 int result = calc(5, 3); // デリゲート経由でメソッド呼び出し /* 内部的な動作の流れ 1. デリゲートインスタンスが作成される 2. Addメソッドへの参照が保持される 3. calc(5, 3)呼び出し時に、保持されているAddメソッドが実行される */
3. マルチキャストの仕組み
// マルチキャストデリゲートの例
public delegate void LogDelegate(string message);
LogDelegate log = (msg) => Console.WriteLine($"Console: {msg}");
log += (msg) => File.AppendAllText("log.txt", $"File: {msg}\n");
// 両方のメソッドが順番に実行される
log("Test Message"); // コンソールとファイルの両方に出力
このように、デリゲートは.NET Frameworkの重要な機能の1つとして、コードの柔軟性と再利用性を高めるために広く活用されています。
次のセクションでは、より実践的な使用方法について詳しく解説していきます。
Delegateの基本的な使い方をマスターしよう
Delegateの宣言と初期化のベストプラクティス
Delegateの宣言と初期化には、いくつかの重要なベストプラクティスがあります。
以下で、具体的な実装パターンと共に解説します。
1. 標準デリゲートの活用
// 非推奨: カスタムデリゲートの定義
public delegate void CustomProcessDelegate(string input);
// 推奨: 標準デリゲートの使用
public void ProcessWithAction(Action<string> processor) {
// Actionを使用した実装
}
// 使用例
Action<string> logger = Console.WriteLine;
Func<int, int, int> add = (x, y) => x + y;
Predicate<string> isEmpty = string.IsNullOrEmpty;
2. デリゲートの初期化パターン
public class NotificationService {
// デリゲートのフィールド宣言
private readonly Action<string> _notifier;
// コンストラクタでの初期化(推奨)
public NotificationService(Action<string> notifier) {
_notifier = notifier ?? throw new ArgumentNullException(nameof(notifier));
}
// メソッド内での使用
public void SendNotification(string message) {
_notifier(message);
}
}
// 使用例
var emailNotifier = new NotificationService(msg => SendEmail(msg));
var smsNotifier = new NotificationService(msg => SendSMS(msg));
3. null条件演算子の活用
public class EventPublisher {
private Action<string> _onMessageReceived;
public void PublishMessage(string message) {
// null条件演算子を使用して安全に呼び出し
_onMessageReceived?.Invoke(message);
}
}
マルチキャストDelegateの活用方法
マルチキャストDelegateは、複数のメソッドを1つのデリゲートにチェーンする機能を提供します。
public class LoggingService {
// マルチキャストデリゲートの定義
private Action<string> _loggers = null;
// ロガーの追加
public void AddLogger(Action<string> logger) {
_loggers += logger;
}
// ロガーの削除
public void RemoveLogger(Action<string> logger) {
_loggers -= logger;
}
// ログの記録
public void Log(string message) {
_loggers?.Invoke(message);
}
}
// 使用例
var loggingService = new LoggingService();
// 複数のログハンドラーを追加
loggingService.AddLogger(msg => Console.WriteLine($"Console: {msg}"));
loggingService.AddLogger(msg => File.AppendAllText("app.log", $"{msg}\n"));
loggingService.AddLogger(msg => SendToDatabase(msg));
// 全てのロガーが順番に実行される
loggingService.Log("Important message");
ジェネリックDelegateで型安全性を確保
ジェネリックDelegateを使用することで、型安全性を確保しつつ、柔軟な実装が可能になります。
1. 基本的なジェネリックデリゲート
public class DataProcessor<T> {
private readonly Func<T, bool> _validator;
private readonly Action<T> _processor;
public DataProcessor(Func<T, bool> validator, Action<T> processor) {
_validator = validator;
_processor = processor;
}
public void Process(T data) {
if (_validator(data)) {
_processor(data);
}
}
}
// 使用例
var numberProcessor = new DataProcessor<int>(
validator: num => num > 0, // 正の数のみ処理
processor: num => Console.WriteLine($"Processing: {num}")
);
2. ジェネリック制約の活用
public class EntityValidator<T> where T : class {
private readonly List<Func<T, bool>> _validations = new List<Func<T, bool>>();
public void AddValidation(Func<T, bool> validation) {
_validations.Add(validation);
}
public bool Validate(T entity) {
return _validations.All(validation => validation(entity));
}
}
// 使用例
public class User {
public string Name { get; set; }
public string Email { get; set; }
}
var userValidator = new EntityValidator<User>();
userValidator.AddValidation(user => !string.IsNullOrEmpty(user.Name));
userValidator.AddValidation(user => !string.IsNullOrEmpty(user.Email));
userValidator.AddValidation(user => user.Email.Contains("@"));
3. イベントハンドリングでの活用
public class EventHub<T> {
private event Action<T> _eventHandlers;
public void Subscribe(Action<T> handler) {
_eventHandlers += handler;
}
public void Unsubscribe(Action<T> handler) {
_eventHandlers -= handler;
}
public void Publish(T data) {
_eventHandlers?.Invoke(data);
}
}
// 使用例
var messageHub = new EventHub<string>();
messageHub.Subscribe(msg => Console.WriteLine($"Handler 1: {msg}"));
messageHub.Subscribe(msg => Console.WriteLine($"Handler 2: {msg}"));
これらの実装パターンを活用することで、型安全で保守性の高いコードを作成できます。
次のセクションでは、より実践的な活用パターンについて解説していきます。
実践的なDelegate活用パターン7選
コールバック処理の実装
コールバックパターンは、非同期処理や処理の完了通知に効果的です。
public class AsyncProcessor {
public void ProcessAsync(string data, Action<string> onSuccess, Action<Exception> onError) {
try {
// 非同期処理をシミュレート
Task.Run(() => {
// 何らかの処理
var result = ProcessData(data);
onSuccess?.Invoke(result);
}).ContinueWith(t => {
if (t.Exception != null) {
onError?.Invoke(t.Exception);
}
}, TaskContinuationOptions.OnlyOnFaulted);
}
catch (Exception ex) {
onError?.Invoke(ex);
}
}
private string ProcessData(string data) {
// 実際の処理
return $"Processed: {data}";
}
}
// 使用例
var processor = new AsyncProcessor();
processor.ProcessAsync(
"test data",
result => Console.WriteLine($"Success: {result}"),
error => Console.WriteLine($"Error: {error.Message}")
);
イベント処理の柔軟な設計
イベント駆動型アーキテクチャにおける柔軟なイベント処理の実装例です。
public class EventAggregator {
private readonly Dictionary<Type, List<Delegate>> _eventHandlers
= new Dictionary<Type, List<Delegate>>();
public void Subscribe<TEvent>(Action<TEvent> handler) {
var eventType = typeof(TEvent);
if (!_eventHandlers.ContainsKey(eventType)) {
_eventHandlers[eventType] = new List<Delegate>();
}
_eventHandlers[eventType].Add(handler);
}
public void Publish<TEvent>(TEvent eventData) {
var eventType = typeof(TEvent);
if (_eventHandlers.ContainsKey(eventType)) {
foreach (var handler in _eventHandlers[eventType]) {
((Action<TEvent>)handler)(eventData);
}
}
}
}
// イベントの定義
public record UserCreatedEvent(string UserId, string Username);
public record OrderPlacedEvent(string OrderId, decimal Amount);
// 使用例
var eventAggregator = new EventAggregator();
// イベントハンドラーの登録
eventAggregator.Subscribe<UserCreatedEvent>(e =>
Console.WriteLine($"User created: {e.Username}"));
eventAggregator.Subscribe<OrderPlacedEvent>(e =>
Console.WriteLine($"Order placed: {e.Amount:C}"));
// イベントの発行
eventAggregator.Publish(new UserCreatedEvent("U1", "John"));
eventAggregator.Publish(new OrderPlacedEvent("O1", 99.99m));
関数の動的な差し替え
実行時に処理を動的に切り替えるパターンです。
public class PricingStrategy {
private Func<decimal, decimal> _discountStrategy;
public void SetStrategy(Func<decimal, decimal> strategy) {
_discountStrategy = strategy;
}
public decimal CalculatePrice(decimal originalPrice) {
return _discountStrategy?.Invoke(originalPrice) ?? originalPrice;
}
}
// 使用例
var pricing = new PricingStrategy();
// 通常価格(20%割引)
pricing.SetStrategy(price => price * 0.8m);
Console.WriteLine(pricing.CalculatePrice(100)); // 出力: 80
// セール期間中(50%割引)
pricing.SetStrategy(price => price * 0.5m);
Console.WriteLine(pricing.CalculatePrice(100)); // 出力: 50
// 特別セール(1000円以上で30%割引)
pricing.SetStrategy(price => price >= 1000 ? price * 0.7m : price);
Console.WriteLine(pricing.CalculatePrice(1000)); // 出力: 700
Console.WriteLine(pricing.CalculatePrice(900)); // 出力: 900
非同期処理との組み合わせ
非同期処理とDelegateを組み合わせた高度な実装パターンです。
public class AsyncOperationManager {
private readonly Dictionary<string, Func<Task>> _operations
= new Dictionary<string, Func<Task>>();
public void RegisterOperation(string name, Func<Task> operation) {
_operations[name] = operation;
}
public async Task ExecuteOperationAsync(string name) {
if (_operations.TryGetValue(name, out var operation)) {
await operation();
}
}
public async Task ExecuteAllAsync() {
var tasks = _operations.Values.Select(op => op());
await Task.WhenAll(tasks);
}
}
// 使用例
var manager = new AsyncOperationManager();
// 非同期操作の登録
manager.RegisterOperation("DataLoad", async () => {
await Task.Delay(1000); // データロードをシミュレート
Console.WriteLine("Data loaded");
});
manager.RegisterOperation("Calculation", async () => {
await Task.Delay(500); // 計算をシミュレート
Console.WriteLine("Calculation completed");
});
// 実行
await manager.ExecuteAllAsync();
戦略パターンの実装
戦略パターンをDelegateを使って軽量に実装する例です。
public class SortingContext {
private readonly Dictionary<string, Func<IEnumerable<int>, IEnumerable<int>>> _sortingStrategies
= new Dictionary<string, Func<IEnumerable<int>, IEnumerable<int>>>();
public SortingContext() {
// デフォルトの並び替え戦略を登録
_sortingStrategies["ascending"] = numbers => numbers.OrderBy(n => n);
_sortingStrategies["descending"] = numbers => numbers.OrderByDescending(n => n);
_sortingStrategies["evenFirst"] = numbers =>
numbers.OrderBy(n => n % 2).ThenBy(n => n);
}
public void AddStrategy(string name, Func<IEnumerable<int>, IEnumerable<int>> strategy) {
_sortingStrategies[name] = strategy;
}
public IEnumerable<int> Sort(string strategyName, IEnumerable<int> numbers) {
return _sortingStrategies.TryGetValue(strategyName, out var strategy)
? strategy(numbers)
: numbers;
}
}
// 使用例
var sorter = new SortingContext();
var numbers = new[] { 1, 4, 2, 8, 5, 3 };
// 異なる戦略で並び替え
var ascending = sorter.Sort("ascending", numbers);
var evenFirst = sorter.Sort("evenFirst", numbers);
チェーン処理の実装
処理を連鎖的に実行するパターンです。
public class RequestProcessor {
private readonly List<Func<string, bool>> _validators
= new List<Func<string, bool>>();
private readonly List<Action<string>> _processors
= new List<Action<string>>();
public RequestProcessor AddValidator(Func<string, bool> validator) {
_validators.Add(validator);
return this;
}
public RequestProcessor AddProcessor(Action<string> processor) {
_processors.Add(processor);
return this;
}
public void Process(string request) {
// 全てのバリデーションをチェック
if (_validators.All(validator => validator(request))) {
// 全ての処理を順番に実行
_processors.ForEach(processor => processor(request));
}
}
}
// 使用例
var processor = new RequestProcessor()
.AddValidator(req => !string.IsNullOrEmpty(req))
.AddValidator(req => req.Length <= 100)
.AddProcessor(req => Console.WriteLine($"Logging: {req}"))
.AddProcessor(req => Console.WriteLine($"Processing: {req}"))
.AddProcessor(req => Console.WriteLine($"Storing: {req}"));
processor.Process("Test Request");
オブザーバーパターンの実装
イベント通知を効率的に実装するパターンです。
public class Observable<T> {
private readonly List<Action<T>> _observers = new List<Action<T>>();
public IDisposable Subscribe(Action<T> observer) {
_observers.Add(observer);
return new Unsubscriber(() => _observers.Remove(observer));
}
public void Notify(T value) {
foreach(var observer in _observers.ToArray()) {
observer(value);
}
}
private class Unsubscriber : IDisposable {
private readonly Action _unsubscribe;
public Unsubscriber(Action unsubscribe) {
_unsubscribe = unsubscribe;
}
public void Dispose() {
_unsubscribe?.Invoke();
}
}
}
// 使用例
var observable = new Observable<string>();
// オブザーバーの登録
using var subscription1 = observable.Subscribe(msg =>
Console.WriteLine($"Observer 1: {msg}"));
using var subscription2 = observable.Subscribe(msg =>
Console.WriteLine($"Observer 2: {msg}"));
// 通知の発行
observable.Notify("Hello, Observers!");
これらのパターンは、実際のプロジェクトで頻繁に使用される実践的な実装例です。
状況に応じて適切なパターンを選択し、必要に応じてカスタマイズすることで、効率的で保守性の高いコードを実現できます。
Delegateのパフォーマンス最適化
Delegateのメモリ使用量を最適化する方法
Delegateのメモリ使用を最適化するには、以下の手法が効果的です。
1. 静的メソッドの活用
public class MemoryOptimization {
// 非推奨: インスタンスメソッドをデリゲートとして使用
private readonly Action<string> _instanceLogger;
// 推奨: 静的メソッドをデリゲートとして使用
private static readonly Action<string> _staticLogger = LogMessage;
public MemoryOptimization() {
// インスタンスメソッドの場合、毎回新しいデリゲートインスタンスが作成される
_instanceLogger = LogMessage;
}
private static void LogMessage(string message) {
Console.WriteLine(message);
}
}
2. デリゲートのキャッシュ化
public class DelegateCache {
// デリゲートをキャッシュするディクショナリ
private static readonly Dictionary<string, Delegate> _cachedDelegates
= new Dictionary<string, Delegate>();
public static TDelegate GetCachedDelegate<TDelegate>(string key, Func<TDelegate> factory)
where TDelegate : Delegate {
if (!_cachedDelegates.TryGetValue(key, out var cached)) {
cached = factory();
_cachedDelegates[key] = cached;
}
return (TDelegate)cached;
}
// 使用例
public static void Example() {
var logDelegate = GetCachedDelegate<Action<string>>("log",
() => message => Console.WriteLine(message));
}
}
3. メソッドグループの最適化
public class MethodGroupOptimization {
// 非推奨: ラムダ式を使用(新しいデリゲートインスタンスが作成される)
private readonly Action<string> _lambdaLogger = s => Console.WriteLine(s);
// 推奨: メソッドグループを使用(既存のメソッド参照を使用)
private readonly Action<string> _methodGroupLogger = Console.WriteLine;
}
Delegateの実行速度を向上させるテクニック
1. インライン展開の活用
public class InlineOptimization {
// [MethodImpl(MethodImplOptions.AggressiveInlining)]属性を使用
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FastOperation(int x, int y) => x + y;
public void ProcessData(int[] data) {
// 単純な処理はラムダ式よりもインライン展開可能なメソッドを使用
Func<int, int, int> operation = FastOperation;
for (int i = 0; i < data.Length - 1; i++) {
data[i] = operation(data[i], data[i + 1]);
}
}
}
2. マルチキャストデリゲートの最適化
public class MulticastOptimization {
private static readonly List<Action<string>> _handlers
= new List<Action<string>>();
// 非推奨: マルチキャストデリゲートの動的な追加/削除
private static event Action<string> _events;
// 推奨: リストベースの管理
public static void AddHandler(Action<string> handler) {
_handlers.Add(handler);
}
public static void RemoveHandler(Action<string> handler) {
_handlers.Remove(handler);
}
public static void RaiseEvent(string message) {
foreach (var handler in _handlers.ToArray()) {
handler(message);
}
}
}
3. パフォーマンス測定例
public class DelegatePerformanceBenchmark {
private static readonly Stopwatch _stopwatch = new Stopwatch();
public static void MeasurePerformance() {
const int iterations = 1000000;
// 直接メソッド呼び出し
_stopwatch.Restart();
for (int i = 0; i < iterations; i++) {
DirectMethod();
}
Console.WriteLine($"Direct method: {_stopwatch.ElapsedMilliseconds}ms");
// デリゲート経由の呼び出し
Action delegateMethod = DirectMethod;
_stopwatch.Restart();
for (int i = 0; i < iterations; i++) {
delegateMethod();
}
Console.WriteLine($"Delegate method: {_stopwatch.ElapsedMilliseconds}ms");
// ラムダ式経由の呼び出し
Action lambdaMethod = () => DirectMethod();
_stopwatch.Restart();
for (int i = 0; i < iterations; i++) {
lambdaMethod();
}
Console.WriteLine($"Lambda method: {_stopwatch.ElapsedMilliseconds}ms");
}
private static void DirectMethod() { }
}
パフォーマンス最適化のポイント
| 最適化項目 | 効果 | 適用シーン |
|---|---|---|
| 静的メソッドの使用 | メモリ使用量の削減 | 状態を持たない処理 |
| デリゲートのキャッシュ | インスタンス生成コストの削減 | 頻繁に使用するデリゲート |
| メソッドグループの活用 | メモリ割り当ての最小化 | シンプルな処理の委譲 |
| インライン展開 | 実行速度の向上 | パフォーマンスクリティカルな処理 |
これらの最適化手法は、アプリケーションの要件や制約に応じて適切に選択してください。
過度な最適化は可読性やメンテナンス性を損なう可能性があるため、実際のパフォーマンス測定結果に基づいて判断することが重要です。
Delegateを使用する際の注意点
メモリリークを防ぐための3つの対策
1. イベントハンドラの適切な解除
public class EventPublisher {
public event EventHandler<string> MessageReceived;
public void PublishMessage(string message) {
MessageReceived?.Invoke(this, message);
}
}
public class EventSubscriber : IDisposable {
private readonly EventPublisher _publisher;
public EventSubscriber(EventPublisher publisher) {
_publisher = publisher;
// イベントの購読
_publisher.MessageReceived += HandleMessage;
}
private void HandleMessage(object sender, string message) {
Console.WriteLine($"Received: {message}");
}
public void Dispose() {
// 重要: イベントハンドラの解除を忘れずに行う
_publisher.MessageReceived -= HandleMessage;
}
}
// 使用例
public void Example() {
var publisher = new EventPublisher();
using var subscriber = new EventSubscriber(publisher);
publisher.PublishMessage("Test");
} // using文によってDisposeが呼ばれ、イベントハンドラが解除される
2. クロージャによるメモリリークの防止
public class ClosureExample {
private class LargeObject {
public byte[] Data = new byte[1000000]; // 1MB
}
// 非推奨: クロージャが大きなオブジェクトへの参照を保持
public Action CreateLeakyDelegate() {
var largeObject = new LargeObject();
// クロージャがlargeObjectへの参照を保持し続ける
return () => Console.WriteLine(largeObject.Data.Length);
}
// 推奨: 必要な情報のみをキャプチャ
public Action CreateOptimizedDelegate() {
var dataLength = new LargeObject().Data.Length;
// 値のみをキャプチャし、オブジェクト全体は保持しない
return () => Console.WriteLine(dataLength);
}
}
3. WeakReferenceの活用
public class WeakDelegateHandler<T> where T : class {
private readonly WeakReference<T> _targetReference;
private readonly MethodInfo _method;
public WeakDelegateHandler(T target, MethodInfo method) {
_targetReference = new WeakReference<T>(target);
_method = method;
}
public bool TryInvoke(params object[] args) {
if (_targetReference.TryGetTarget(out var target)) {
_method.Invoke(target, args);
return true;
}
return false;
}
}
// 使用例
public class WeakDelegateExample {
private readonly List<WeakDelegateHandler<object>> _handlers
= new List<WeakDelegateHandler<object>>();
public void AddHandler(object target, MethodInfo method) {
_handlers.Add(new WeakDelegateHandler<object>(target, method));
}
public void RaiseEvent(object arg) {
// 無効になったハンドラを削除しながら実行
_handlers.RemoveAll(handler => !handler.TryInvoke(arg));
}
}
マルチスレッド環境での安全な使用方法
1. デリゲートの同期的な実行
public class ThreadSafeEventManager {
private readonly object _lockObject = new object();
private event Action<string> _handlers;
public void AddHandler(Action<string> handler) {
lock (_lockObject) {
_handlers += handler;
}
}
public void RemoveHandler(Action<string> handler) {
lock (_lockObject) {
_handlers -= handler;
}
}
public void RaiseEvent(string message) {
Action<string> handlers;
lock (_lockObject) {
handlers = _handlers;
}
// コピーしたデリゲートを安全に実行
handlers?.Invoke(message);
}
}
2. 非同期デリゲートの安全な実行
public class AsyncEventManager {
private readonly List<Func<string, Task>> _asyncHandlers
= new List<Func<string, Task>>();
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
public async Task AddHandlerAsync(Func<string, Task> handler) {
await _semaphore.WaitAsync();
try {
_asyncHandlers.Add(handler);
}
finally {
_semaphore.Release();
}
}
public async Task RaiseEventAsync(string message) {
List<Func<string, Task>> handlers;
await _semaphore.WaitAsync();
try {
handlers = new List<Func<string, Task>>(_asyncHandlers);
}
finally {
_semaphore.Release();
}
// 全てのハンドラを並列実行
var tasks = handlers.Select(h => h(message));
await Task.WhenAll(tasks);
}
}
// 使用例
public class AsyncEventExample {
private readonly AsyncEventManager _eventManager = new AsyncEventManager();
public async Task RunExample() {
await _eventManager.AddHandlerAsync(async message => {
await Task.Delay(100);
Console.WriteLine($"Handler 1: {message}");
});
await _eventManager.AddHandlerAsync(async message => {
await Task.Delay(200);
Console.WriteLine($"Handler 2: {message}");
});
await _eventManager.RaiseEventAsync("Test Message");
}
}
3. スレッドセーフなキャッシュの実装
public class ThreadSafeDelegateCache {
private readonly ConcurrentDictionary<string, Delegate> _cache
= new ConcurrentDictionary<string, Delegate>();
public TDelegate GetOrCreate<TDelegate>(string key, Func<TDelegate> factory)
where TDelegate : Delegate {
return (TDelegate)_cache.GetOrAdd(key, _ => factory());
}
// 使用例
public void Example() {
var handler = GetOrCreate("handler",
() => new Action<string>(message =>
Console.WriteLine($"Handled: {message}")));
}
}
マルチスレッド環境でのデリゲート使用時の注意点まとめ
| 状況 | 推奨される対策 | 理由 |
|---|---|---|
| イベント発火 | デリゲートのコピーを使用 | 実行中のコレクション変更を防ぐ |
| ハンドラの追加/削除 | 同期機構の使用 | 競合状態を防ぐ |
| 非同期処理 | TaskベースのAPI設計 | 適切な非同期処理の実現 |
| キャッシュ | ConcurrentDictionaryの使用 | スレッドセーフな操作の保証 |
これらの注意点を適切に考慮することで、メモリリークを防ぎ、マルチスレッド環境でも安全に動作するコードを実装できます。
特に大規模なアプリケーションでは、これらの問題が重要になってきます。
C# Delegateの活用事例と実装例
実際のプロジェクトでの活用例
1. プラグイン機構の実装
public class PluginManager {
private readonly Dictionary<string, Func<IPlugin>> _pluginFactories
= new Dictionary<string, Func<IPlugin>>();
public void RegisterPlugin(string name, Func<IPlugin> factory) {
_pluginFactories[name] = factory;
}
public IPlugin CreatePlugin(string name) {
return _pluginFactories.TryGetValue(name, out var factory)
? factory()
: null;
}
}
public interface IPlugin {
string Name { get; }
void Execute();
}
// プラグイン実装例
public class LoggingPlugin : IPlugin {
public string Name => "Logging";
public void Execute() {
Console.WriteLine("Logging plugin executed");
}
}
// 使用例
public class PluginExample {
public void ConfigurePlugins() {
var manager = new PluginManager();
// プラグインの登録
manager.RegisterPlugin("logging", () => new LoggingPlugin());
// プラグインの使用
var plugin = manager.CreatePlugin("logging");
plugin?.Execute();
}
}
2. バリデーションフレームワークの実装
public class ValidationFramework<T> {
private readonly List<Func<T, ValidationResult>> _validators
= new List<Func<T, ValidationResult>>();
public void AddValidator(Func<T, ValidationResult> validator) {
_validators.Add(validator);
}
public ValidationResult Validate(T entity) {
var errors = new List<string>();
foreach (var validator in _validators) {
var result = validator(entity);
if (!result.IsValid) {
errors.AddRange(result.Errors);
}
}
return new ValidationResult(errors);
}
}
public class ValidationResult {
public bool IsValid => !Errors.Any();
public List<string> Errors { get; }
public ValidationResult(List<string> errors) {
Errors = errors ?? new List<string>();
}
}
// 使用例
public class User {
public string Username { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
public class UserValidation {
public ValidationFramework<User> CreateValidator() {
var validator = new ValidationFramework<User>();
// バリデーションルールの追加
validator.AddValidator(user => {
var errors = new List<string>();
if (string.IsNullOrEmpty(user.Username)) {
errors.Add("Username is required");
}
if (string.IsNullOrEmpty(user.Email) || !user.Email.Contains("@")) {
errors.Add("Valid email is required");
}
if (user.Age < 18) {
errors.Add("User must be 18 or older");
}
return new ValidationResult(errors);
});
return validator;
}
}
サンプルコードで学ぶ実装パターン
1. ワークフロー実行エンジンの実装
public class WorkflowEngine {
private readonly Dictionary<string, Func<WorkflowContext, Task>> _activities
= new Dictionary<string, Func<WorkflowContext, Task>>();
public void RegisterActivity(string name, Func<WorkflowContext, Task> activity) {
_activities[name] = activity;
}
public async Task ExecuteWorkflowAsync(WorkflowDefinition workflow) {
var context = new WorkflowContext();
foreach (var activityName in workflow.Activities) {
if (_activities.TryGetValue(activityName, out var activity)) {
await activity(context);
}
}
}
}
public class WorkflowContext {
public Dictionary<string, object> Data { get; }
= new Dictionary<string, object>();
}
public class WorkflowDefinition {
public List<string> Activities { get; set; } = new List<string>();
}
// 使用例
public class WorkflowExample {
public async Task RunWorkflowAsync() {
var engine = new WorkflowEngine();
// アクティビティの登録
engine.RegisterActivity("validate", async context => {
await Task.Delay(100); // バリデーション処理
Console.WriteLine("Validation completed");
});
engine.RegisterActivity("process", async context => {
await Task.Delay(200); // データ処理
Console.WriteLine("Processing completed");
});
engine.RegisterActivity("notify", async context => {
await Task.Delay(100); // 通知処理
Console.WriteLine("Notification sent");
});
// ワークフローの実行
var workflow = new WorkflowDefinition {
Activities = new List<string> { "validate", "process", "notify" }
};
await engine.ExecuteWorkflowAsync(workflow);
}
}
2. イベントベースの監視システムの実装
public class MonitoringSystem {
private readonly Dictionary<string, List<Action<MonitoringEvent>>> _handlers
= new Dictionary<string, List<Action<MonitoringEvent>>>();
public void Subscribe(string eventType, Action<MonitoringEvent> handler) {
if (!_handlers.ContainsKey(eventType)) {
_handlers[eventType] = new List<Action<MonitoringEvent>>();
}
_handlers[eventType].Add(handler);
}
public void PublishEvent(string eventType, MonitoringEvent eventData) {
if (_handlers.TryGetValue(eventType, out var handlers)) {
foreach (var handler in handlers) {
handler(eventData);
}
}
}
}
public class MonitoringEvent {
public DateTime Timestamp { get; set; }
public string Message { get; set; }
public EventSeverity Severity { get; set; }
}
public enum EventSeverity {
Info,
Warning,
Error,
Critical
}
// 使用例
public class MonitoringExample {
public void ConfigureMonitoring() {
var monitor = new MonitoringSystem();
// イベントハンドラーの登録
monitor.Subscribe("system", e => {
Console.WriteLine($"System event: {e.Message} ({e.Severity})");
});
monitor.Subscribe("security", e => {
if (e.Severity >= EventSeverity.Warning) {
Console.WriteLine($"Security alert: {e.Message}");
}
});
// イベントの発行
monitor.PublishEvent("system", new MonitoringEvent {
Timestamp = DateTime.Now,
Message = "System startup completed",
Severity = EventSeverity.Info
});
monitor.PublishEvent("security", new MonitoringEvent {
Timestamp = DateTime.Now,
Message = "Invalid login attempt detected",
Severity = EventSeverity.Warning
});
}
}
これらの実装例は、実際のプロジェクトで使用される可能性が高い、実践的なパターンです。デリゲートを活用することで、柔軟で拡張性の高いシステムを構築できます。
特に、プラグインアーキテクチャ、バリデーション、ワークフロー管理、イベント処理などの実装で効果を発揮します。
これらのパターンを基に、プロジェクトの要件に合わせてカスタマイズすることで、保守性が高く、拡張しやすいコードを実現できます。
Delegateのまとめ
Delegateは、C#における重要な言語機能の一つであり、コードの疎結合化やイベント処理、動的な振る舞いの実現に大きく貢献します。
本記事で解説した実装パターンやベストプラクティスを活用することで、メンテナンス性が高く、拡張性のある堅牢なアプリケーションを開発することができます。
Delegateを使用することで、コンポーネント間の疎結合化が実現でき、保守性が向上する
マルチキャストDelegateやジェネリックDelegateを活用することで、より柔軟な実装が可能
パフォーマンスとメモリ管理を考慮したDelegateの使用方法が重要
イベント処理やプラグイン機構など、実際のプロジェクトで活用できる具体的なパターンが多数存在する
マルチスレッド環境での適切な使用方法を理解することで、安全なアプリケーションを構築できる
標準デリゲート(Action、Func、Predicate)を積極的に活用することで、コードの可読性が向上する
これらの要素を記事の冒頭と末尾に配置することで、読者は記事の内容を効率的に理解し、実践に活かすことができます。

