Skip to content

Commit b10cb87

Browse files
authored
feat(TcpSocket): remove SetDataPackageAdapter method (#550)
* refactor: 移除 Set 方法 * refactor: 重命名静态变量 * refactor: 更新缓存逻辑 * refactor: 更新逻辑 * test: 更新单元测试 * chore: bump version 9.0.4 * chore: bump version 9.0.3 * test: 更新单元测试 * doc: 增加注释 * chore: bump version 9.0.4
1 parent 6f8d8aa commit b10cb87

5 files changed

Lines changed: 119 additions & 130 deletions

File tree

src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<Version>9.0.3</Version>
4+
<Version>9.0.4</Version>
55
</PropertyGroup>
66

77
<PropertyGroup>

src/extensions/BootstrapBlazor.Socket/DataConverter/DataConverter.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ public class DataConverter<TEntity>(DataConverterCollections converters) : IData
1717
/// </summary>
1818
public DataConverter() : this(new())
1919
{
20-
2120
}
2221

2322
/// <summary>
@@ -28,9 +27,19 @@ public DataConverter() : this(new())
2827
/// <returns></returns>
2928
public virtual bool TryConvertTo(ReadOnlyMemory<byte> data, [NotNullWhen(true)] out TEntity? entity)
3029
{
31-
var v = CreateEntity();
32-
var ret = Parse(data, v);
33-
entity = ret ? v : default;
30+
var ret = false;
31+
entity = default;
32+
try
33+
{
34+
var v = CreateEntity();
35+
if (Parse(data, v))
36+
{
37+
entity = v;
38+
ret = true;
39+
}
40+
}
41+
catch { }
42+
3443
return ret;
3544
}
3645

@@ -57,8 +66,7 @@ protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
5766
var properties = entity.GetType().GetProperties().Where(p => p.CanWrite).ToList();
5867
foreach (var p in properties)
5968
{
60-
var attr = p.GetCustomAttribute<DataPropertyConverterAttribute>(false)
61-
?? GetPropertyConverterAttribute(p);
69+
var attr = p.GetCustomAttribute<DataPropertyConverterAttribute>(false) ?? GetPropertyConverterAttribute(p);
6270
if (attr is { Type: not null })
6371
{
6472
var value = attr.ConvertTo(data);
@@ -69,8 +77,10 @@ protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
6977
}
7078
}
7179
}
80+
7281
ret = true;
7382
}
83+
7484
return ret;
7585
}
7686

@@ -81,6 +91,7 @@ protected virtual bool Parse(ReadOnlyMemory<byte> data, TEntity entity)
8191
{
8292
attr = v;
8393
}
94+
8495
return attr;
8596
}
8697
}

src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<Version>9.0.2</Version>
4+
<Version>9.0.4</Version>
55
</PropertyGroup>
66

77
<PropertyGroup>

src/extensions/BootstrapBlazor.TcpSocket/Extensions/ITcpSocketClientExtensions.cs

Lines changed: 86 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public static class ITcpSocketClientExtensions
2020
/// Sends the specified string content to the connected TCP socket client asynchronously.
2121
/// </summary>
2222
/// <remarks>This method converts the provided string content into a byte array using the specified
23-
/// encoding (or UTF-8 by default) and sends it to the connected TCP socket client. Ensure the client is connected
23+
/// encoding (or UTF-8 by default) and sends it to the connected TCP socket client. Ensure the client is connected
2424
/// before calling this method.</remarks>
2525
/// <param name="client">The TCP socket client to which the content will be sent. Cannot be <see langword="null"/>.</param>
2626
/// <param name="content">The string content to send. Cannot be <see langword="null"/> or empty.</param>
@@ -51,7 +51,7 @@ public static ValueTask<bool> ConnectAsync(this ITcpSocketClient client, string
5151
return client.ConnectAsync(endPoint, token);
5252
}
5353

54-
private static readonly Dictionary<ITcpSocketClient, List<(IDataPackageAdapter Adapter, Func<ReadOnlyMemory<byte>, ValueTask> Callback)>> _cache = [];
54+
private static readonly Dictionary<ITcpSocketClient, List<(IDataPackageAdapter Adapter, Func<ReadOnlyMemory<byte>, ValueTask> Callback)>> Cache = [];
5555

5656
/// <summary>
5757
/// 增加 <see cref="ITcpSocketClient"/> 数据适配器及其对应的回调方法
@@ -61,25 +61,25 @@ public static ValueTask<bool> ConnectAsync(this ITcpSocketClient client, string
6161
/// <param name="callback"></param>
6262
public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<ReadOnlyMemory<byte>, ValueTask> callback)
6363
{
64-
if (_cache.TryGetValue(client, out var list))
64+
async ValueTask ReceivedCallback(ReadOnlyMemory<byte> buffer)
6565
{
66-
list.Add((adapter, cb));
66+
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
67+
await adapter.HandlerAsync(buffer);
68+
}
69+
70+
if (Cache.TryGetValue(client, out var list))
71+
{
72+
list.Add((adapter, ReceivedCallback));
6773
}
6874
else
6975
{
70-
_cache.Add(client, [(adapter, cb)]);
76+
Cache.Add(client, [(adapter, ReceivedCallback)]);
7177
}
7278

73-
client.ReceivedCallBack += cb;
79+
client.ReceivedCallBack += ReceivedCallback;
7480

7581
// 设置 DataPackageAdapter 的回调函数
7682
adapter.ReceivedCallBack = callback;
77-
78-
async ValueTask cb(ReadOnlyMemory<byte> buffer)
79-
{
80-
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
81-
await adapter.HandlerAsync(buffer);
82-
}
8383
}
8484

8585
/// <summary>
@@ -89,7 +89,7 @@ async ValueTask cb(ReadOnlyMemory<byte> buffer)
8989
/// <param name="callback"></param>
9090
public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func<ReadOnlyMemory<byte>, ValueTask> callback)
9191
{
92-
if (_cache.TryGetValue(client, out var list))
92+
if (Cache.TryGetValue(client, out var list))
9393
{
9494
var items = list.Where(i => i.Adapter.ReceivedCallBack == callback).ToList();
9595
foreach (var c in items)
@@ -101,55 +101,21 @@ public static void RemoveDataPackageAdapter(this ITcpSocketClient client, Func<R
101101
}
102102

103103
/// <summary>
104-
/// Configures the specified <see cref="ITcpSocketClient"/> to use the provided <see cref="IDataPackageAdapter"/>
105-
/// for processing received data and sets a callback to handle processed data.
106-
/// </summary>
107-
/// <remarks>This method sets up a two-way data processing pipeline: <list type="bullet"> <item>
108-
/// <description>The <paramref name="client"/> is configured to pass received data to the <paramref name="adapter"/>
109-
/// for processing.</description> </item> <item> <description>The <paramref name="adapter"/> is configured to invoke
110-
/// the provided <paramref name="callback"/> with the processed data.</description> </item> </list> Use this method
111-
/// to integrate a custom data processing adapter with a TCP socket client.</remarks>
112-
/// <param name="client">The <see cref="ITcpSocketClient"/> instance to configure.</param>
113-
/// <param name="adapter">The <see cref="IDataPackageAdapter"/> used to process incoming data.</param>
114-
/// <param name="callback">A callback function invoked with the processed data. The function receives a <see cref="ReadOnlyMemory{T}"/>
115-
/// containing the processed data and returns a <see cref="ValueTask"/>.</param>
116-
public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<ReadOnlyMemory<byte>, ValueTask> callback)
117-
{
118-
// 释放缓存
119-
if (_cache.TryGetValue(client, out var list))
120-
{
121-
foreach (var (Adapter, Callback) in list)
122-
{
123-
client.ReceivedCallBack -= Callback;
124-
}
125-
list.Clear();
126-
}
127-
128-
// 设置 ITcpSocketClient 的回调函数
129-
client.ReceivedCallBack = async buffer =>
130-
{
131-
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
132-
await adapter.HandlerAsync(buffer);
133-
};
134-
135-
// 设置 DataPackageAdapter 的回调函数
136-
adapter.ReceivedCallBack = callback;
137-
}
138-
139-
/// <summary>
140-
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法
104+
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法,切记使用 <see cref="RemoveDataPackageAdapter(ITcpSocketClient, Func{ReadOnlyMemory{byte}, ValueTask})"/> 移除数据处理委托防止内存泄露
141105
/// </summary>
142106
/// <param name="client"><see cref="ITcpSocketClient"/> 实例</param>
143107
/// <param name="handler"><see cref="IDataPackageHandler"/> 数据处理实例</param>
144108
/// <param name="callback">回调方法</param>
145-
public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func<ReadOnlyMemory<byte>, ValueTask> callback)
109+
public static void AddDataPackageAdapter(this ITcpSocketClient client, IDataPackageHandler handler, Func<ReadOnlyMemory<byte>, ValueTask> callback)
146110
{
147-
client.SetDataPackageAdapter(new DataPackageAdapter(handler), callback);
111+
client.AddDataPackageAdapter(new DataPackageAdapter(handler), callback);
148112
}
149113

114+
private static readonly Dictionary<ITcpSocketClient, List<(Func<ReadOnlyMemory<byte>, ValueTask> ReceivedCallback, Delegate EntityCallback)>> EntityCache = [];
115+
150116
/// <summary>
151117
/// Configures the specified <see cref="ITcpSocketClient"/> to use a data package adapter and a callback function
152-
/// for processing received data.
118+
/// for processing received data. 切记使用 <see cref="RemoveDataPackageAdapter(ITcpSocketClient, Func{ReadOnlyMemory{byte}, ValueTask})"/> 移除数据处理委托防止内存泄露
153119
/// </summary>
154120
/// <remarks>This method sets up the <paramref name="client"/> to process incoming data using the
155121
/// specified <paramref name="adapter"/> and <paramref name="socketDataConverter"/>. The <paramref
@@ -159,24 +125,24 @@ public static void SetDataPackageAdapter(this ITcpSocketClient client, IDataPack
159125
/// <param name="adapter">The data package adapter responsible for handling incoming data.</param>
160126
/// <param name="socketDataConverter">The converter used to transform the received data into the specified entity type.</param>
161127
/// <param name="callback">The callback function to be invoked with the converted entity.</param>
162-
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, IDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
128+
public static void AddDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, IDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
163129
{
164-
// 释放缓存
165-
if (_cache.TryGetValue(client, out var list))
130+
async ValueTask ReceivedCallback(ReadOnlyMemory<byte> buffer)
166131
{
167-
foreach (var (Adapter, Callback) in list)
168-
{
169-
client.ReceivedCallBack -= Callback;
170-
}
171-
list.Clear();
132+
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
133+
await adapter.HandlerAsync(buffer);
172134
}
173135

174-
// 设置 ITcpSocketClient 的回调函数
175-
client.ReceivedCallBack = async buffer =>
136+
if (EntityCache.TryGetValue(client, out var list))
176137
{
177-
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
178-
await adapter.HandlerAsync(buffer);
179-
};
138+
list.Add((ReceivedCallback, callback));
139+
}
140+
else
141+
{
142+
EntityCache.Add(client, [(ReceivedCallback, callback)]);
143+
}
144+
145+
client.ReceivedCallBack += ReceivedCallback;
180146

181147
// 设置 DataPackageAdapter 的回调函数
182148
adapter.ReceivedCallBack = async buffer =>
@@ -191,21 +157,39 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
191157
}
192158

193159
/// <summary>
194-
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法
160+
/// 移除 <see cref="ITcpSocketClient"/> 数据适配器及其对应的回调方法
161+
/// </summary>
162+
/// <param name="client"></param>
163+
/// <param name="callback"></param>
164+
public static void RemoveDataPackageAdapter<TEntity>(this ITcpSocketClient client, Func<TEntity?, Task> callback)
165+
{
166+
if (EntityCache.TryGetValue(client, out var list))
167+
{
168+
var items = list.Where(i => i.EntityCallback.Equals(callback)).ToList();
169+
foreach (var c in items)
170+
{
171+
client.ReceivedCallBack -= c.ReceivedCallback;
172+
list.Remove(c);
173+
}
174+
}
175+
}
176+
177+
/// <summary>
178+
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法。切记使用 <see cref="RemoveDataPackageAdapter"/> 移除数据处理委托防止内存泄露
195179
/// </summary>
196180
/// <typeparam name="TEntity"></typeparam>
197181
/// <param name="client"></param>
198182
/// <param name="handler"></param>
199183
/// <param name="socketDataConverter"></param>
200184
/// <param name="callback"></param>
201-
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageHandler handler, IDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
185+
public static void AddDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageHandler handler, IDataConverter<TEntity> socketDataConverter, Func<TEntity?, Task> callback)
202186
{
203-
client.SetDataPackageAdapter(new DataPackageAdapter(handler), socketDataConverter, callback);
187+
client.AddDataPackageAdapter(new DataPackageAdapter(handler), socketDataConverter, callback);
204188
}
205189

206190
/// <summary>
207191
/// Configures the specified <see cref="ITcpSocketClient"/> to use a custom data package adapter and callback
208-
/// function.
192+
/// function. 切记使用 <see cref="RemoveDataPackageAdapter"/> 移除数据处理委托防止内存泄露
209193
/// </summary>
210194
/// <remarks>This method sets up the <paramref name="client"/> to use the specified <paramref
211195
/// name="adapter"/> for handling incoming data. If the <typeparamref name="TEntity"/> type is decorated with a <see
@@ -216,74 +200,65 @@ public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client,
216200
/// <param name="client">The TCP socket client to configure.</param>
217201
/// <param name="adapter">The data package adapter responsible for processing incoming data.</param>
218202
/// <param name="callback">The callback function to invoke with the processed entity of type <typeparamref name="TEntity"/>.</param>
219-
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
203+
public static void AddDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageAdapter adapter, Func<TEntity?, Task> callback)
220204
{
221-
// 释放缓存
222-
if (_cache.TryGetValue(client, out var list))
223-
{
224-
foreach (var (Adapter, Callback) in list)
225-
{
226-
client.ReceivedCallBack -= Callback;
227-
}
228-
list.Clear();
229-
}
230-
231-
// 设置 ITcpSocketClient 的回调函数
232-
client.ReceivedCallBack = async buffer =>
205+
async ValueTask ReceivedCallback(ReadOnlyMemory<byte> buffer)
233206
{
234207
// 将接收到的数据传递给 DataPackageAdapter 进行数据处理合规数据触发 ReceivedCallBack 回调
235208
await adapter.HandlerAsync(buffer);
236-
};
237-
238-
IDataConverter<TEntity>? converter = null;
209+
}
239210

240-
var type = typeof(TEntity);
241-
var converterType = type.GetCustomAttribute<DataTypeConverterAttribute>();
242-
if (converterType is { Type: not null })
211+
if (EntityCache.TryGetValue(client, out var list))
243212
{
244-
// 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器
245-
converter = converterType.Type.CreateInstance<IDataConverter<TEntity>>();
213+
list.Add((ReceivedCallback, callback));
246214
}
247215
else
248216
{
249-
// 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器
250-
converter = client.GetSocketDataConverter<TEntity>();
217+
EntityCache.Add(client, [(ReceivedCallback, callback)]);
251218
}
252219

220+
client.ReceivedCallBack += ReceivedCallback;
221+
222+
IDataConverter<TEntity>? converter = null;
223+
224+
var type = typeof(TEntity);
225+
var converterType = type.GetCustomAttribute<DataTypeConverterAttribute>();
226+
227+
// 如果类型上有 SocketDataTypeConverterAttribute 特性则使用特性中指定的转换器
228+
// 如果没有特性则从 ITcpSocketClient 中的服务容器获取转换器
229+
converter = converterType is { Type: not null }
230+
? converterType.Type.CreateInstance<IDataConverter<TEntity>>()
231+
: client.GetSocketDataConverter<TEntity>();
232+
253233
if (converter == null)
254234
{
255-
// 设置正常回调
235+
// 未设置数据转换器返回 default 值
256236
adapter.ReceivedCallBack = async buffer => await callback(default);
257237
}
258238
else
259239
{
260240
// 设置转化器
261-
adapter.SetDataAdapterCallback(converter, callback);
241+
adapter.ReceivedCallBack = async buffer =>
242+
{
243+
TEntity? ret = default;
244+
if (converter.TryConvertTo(buffer, out var t))
245+
{
246+
ret = t;
247+
}
248+
await callback(ret);
249+
};
262250
}
263251
}
264252

265253
/// <summary>
266-
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法
254+
/// 通过指定 <see cref="IDataPackageHandler"/> 数据处理实例,设置数据适配器并配置回调方法。切记使用 <see cref="RemoveDataPackageAdapter"/> 移除数据处理委托防止内存泄露
267255
/// </summary>
268256
/// <param name="client"><see cref="ITcpSocketClient"/> 实例</param>
269257
/// <param name="handler"><see cref="IDataPackageHandler"/> 数据处理实例</param>
270258
/// <param name="callback">回调方法</param>
271-
public static void SetDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageHandler handler, Func<TEntity?, Task> callback)
259+
public static void AddDataPackageAdapter<TEntity>(this ITcpSocketClient client, IDataPackageHandler handler, Func<TEntity?, Task> callback)
272260
{
273-
client.SetDataPackageAdapter(new DataPackageAdapter(handler), callback);
274-
}
275-
276-
private static void SetDataAdapterCallback<TEntity>(this IDataPackageAdapter adapter, IDataConverter<TEntity> converter, Func<TEntity?, Task> callback)
277-
{
278-
adapter.ReceivedCallBack = async buffer =>
279-
{
280-
TEntity? ret = default;
281-
if (converter.TryConvertTo(buffer, out var t))
282-
{
283-
ret = t;
284-
}
285-
await callback(ret);
286-
};
261+
client.AddDataPackageAdapter(new DataPackageAdapter(handler), callback);
287262
}
288263

289264
private static IDataConverter<TEntity>? GetSocketDataConverter<TEntity>(this ITcpSocketClient client)

0 commit comments

Comments
 (0)