C#中如何通过接口结合回调实现WebSocket消息的发布与接收
Hey there, let's break this down step by step—this is a common pattern for decoupling publishers and subscribers using interfaces and callbacks in C#, perfect for your WebSocket scenario. We'll make sure class B only interacts with class A via the contract interface I, keeping everything loosely coupled and maintainable.
1. Define the Contract Interfaces & Delegates
First, we need to lay out the core contracts: a callback signature (to define how messages are received) and the main interface that class A will implement. This ensures class B never depends on class A directly—only on the interface.
// Delegate that defines the shape of our message-received callback public delegate void WebSocketMessageReceivedHandler(string message); // The contract interface: this is the only way class B can communicate with class A public interface IWebSocketPublisher { // Method for subscribers to register their callback void RegisterMessageCallback(WebSocketMessageReceivedHandler callback); // Optional but recommended: Method to unregister callbacks when done void UnregisterMessageCallback(WebSocketMessageReceivedHandler callback); }
2. Implement Class A (WebSocket Publisher)
Class A will handle the WebSocket connection logic, implement IWebSocketPublisher, and trigger all registered callbacks whenever a new message is published/received.
using System.Net.WebSockets; using System.Text; public class WebSocketPublisher : IWebSocketPublisher { // Thread-safe list to store registered callbacks (supports multiple subscribers) private readonly List<WebSocketMessageReceivedHandler> _callbacks = new(); private readonly object _lock = new(); // Example method to start the WebSocket connection and listen for messages public async Task StartWebSocketClient(string wsUrl) { using var client = new ClientWebSocket(); await client.ConnectAsync(new Uri(wsUrl), CancellationToken.None); var buffer = new byte[1024 * 4]; while (client.State == WebSocketState.Open) { var receiveResult = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); if (receiveResult.MessageType == WebSocketMessageType.Text) { string incomingMessage = Encoding.UTF8.GetString(buffer, 0, receiveResult.Count); // Trigger all registered callbacks with the new message TriggerMessageCallbacks(incomingMessage); } } } private void TriggerMessageCallbacks(string message) { // Lock to avoid race conditions if callbacks are added/removed mid-execution lock (_lock) { // Use ToList() to iterate over a copy, preventing errors if the list changes during iteration foreach (var callback in _callbacks.ToList()) { callback.Invoke(message); } } } // Implement interface methods to manage callbacks public void RegisterMessageCallback(WebSocketMessageReceivedHandler callback) { if (callback == null) throw new ArgumentNullException(nameof(callback)); lock (_lock) { _callbacks.Add(callback); } } public void UnregisterMessageCallback(WebSocketMessageReceivedHandler callback) { lock (_lock) { _callbacks.Remove(callback); } } }
3. Implement Class B (Message Subscriber)
Class B will depend solely on IWebSocketPublisher, register its own custom callback logic to process incoming messages, and clean up when it's done.
public class MessageSubscriber : IDisposable { private readonly IWebSocketPublisher _publisher; // Inject the interface (dependency injection is ideal here for even looser coupling) public MessageSubscriber(IWebSocketPublisher publisher) { _publisher = publisher; // Register our callback when the subscriber is initialized _publisher.RegisterMessageCallback(HandleIncomingMessage); } // The callback method that runs when a message is received private void HandleIncomingMessage(string message) { Console.WriteLine($"Class B processed message: {message}"); // Add your custom business logic here (e.g., parse the message, update UI, etc.) } // Clean up the callback to prevent memory leaks public void Dispose() { _publisher.UnregisterMessageCallback(HandleIncomingMessage); } }
4. Putting It All Together
Here's how you'd use these classes in your application:
// Initialize the publisher (class A) behind the interface IWebSocketPublisher publisher = new WebSocketPublisher(); // Initialize the subscriber (class B) with the interface instance var subscriber = new MessageSubscriber(publisher); // Start the WebSocket connection (run in a background thread to avoid blocking) _ = publisher.StartWebSocketClient("wss://your-websocket-endpoint.com"); // When you're done with the subscriber, dispose it to clean up // subscriber.Dispose();
Alternative: Interface-Based Callbacks
If your team prefers using interfaces over delegates for stronger typing, you can adjust the pattern like this:
// Callback interface that subscribers must implement public interface IWebSocketMessageHandler { void OnMessageReceived(string message); } // Updated contract interface public interface IWebSocketPublisher { void RegisterHandler(IWebSocketMessageHandler handler); void UnregisterHandler(IWebSocketMessageHandler handler); } // Updated Class A implementation public class WebSocketPublisher : IWebSocketPublisher { private readonly List<IWebSocketMessageHandler> _handlers = new(); private readonly object _lock = new(); // ... (WebSocket logic remains mostly the same) private void TriggerMessageCallbacks(string message) { lock (_lock) { foreach (var handler in _handlers.ToList()) { handler.OnMessageReceived(message); } } } public void RegisterHandler(IWebSocketMessageHandler handler) { if (handler == null) throw new ArgumentNullException(nameof(handler)); lock (_lock) { _handlers.Add(handler); } } public void UnregisterHandler(IWebSocketMessageHandler handler) { lock (_lock) { _handlers.Remove(handler); } } } // Updated Class B that implements the callback interface public class MessageSubscriber : IWebSocketMessageHandler, IDisposable { private readonly IWebSocketPublisher _publisher; public MessageSubscriber(IWebSocketPublisher publisher) { _publisher = publisher; _publisher.RegisterHandler(this); } public void OnMessageReceived(string message) { Console.WriteLine($"Class B processed message: {message}"); } public void Dispose() { _publisher.UnregisterHandler(this); } }
This approach is just as valid and feels more "interface-first" if that aligns with your coding standards.
内容的提问来源于stack exchange,提问作者Joseph

