mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(events): 添加事件优先级和传播控制功能
- 实现了事件优先级机制,支持按优先级顺序执行事件处理器 - 新增EventPropagation枚举,支持All、UntilHandled和Highest三种传播模式 - 添加Register方法的重载版本,支持指定事件处理器优先级 - 实现Send方法的重载版本,支持指定事件传播模式 - 新增PriorityEvent类处理带优先级的事件逻辑 - 添加IocContainer的CreateScope方法支持服务作用域管理 - 实现RegisterTransient和RegisterScoped方法完善依赖注入生命周期 - 新增SpanExtensions扩展方法提供高性能的span操作功能 - 添加全面的单元测试覆盖事件优先级、IoC容器生命周期和span扩展功能
This commit is contained in:
parent
e2cfa7bffa
commit
e2dca4f5a6
22
GFramework.Core.Abstractions/events/EventPropagation.cs
Normal file
22
GFramework.Core.Abstractions/events/EventPropagation.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace GFramework.Core.Abstractions.events;
|
||||
|
||||
/// <summary>
|
||||
/// 事件传播模式
|
||||
/// </summary>
|
||||
public enum EventPropagation
|
||||
{
|
||||
/// <summary>
|
||||
/// 传播到所有处理器
|
||||
/// </summary>
|
||||
All,
|
||||
|
||||
/// <summary>
|
||||
/// 传播直到某个处理器标记为已处理
|
||||
/// </summary>
|
||||
UntilHandled,
|
||||
|
||||
/// <summary>
|
||||
/// 仅传播到最高优先级的处理器
|
||||
/// </summary>
|
||||
Highest
|
||||
}
|
||||
@ -18,6 +18,14 @@ public interface IEventBus
|
||||
/// <param name="e">事件实例</param>
|
||||
void Send<T>(T e);
|
||||
|
||||
/// <summary>
|
||||
/// 发送指定的事件实例,并指定传播模式
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="e">事件实例</param>
|
||||
/// <param name="propagation">事件传播模式</param>
|
||||
void Send<T>(T e, EventPropagation propagation);
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件监听器
|
||||
/// </summary>
|
||||
@ -26,6 +34,15 @@ public interface IEventBus
|
||||
/// <returns>反注册接口,用于注销事件监听</returns>
|
||||
IUnRegister Register<T>(Action<T> onEvent);
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件监听器,并指定优先级
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="onEvent">事件处理回调函数</param>
|
||||
/// <param name="priority">优先级,数值越大优先级越高</param>
|
||||
/// <returns>反注册接口,用于注销事件监听</returns>
|
||||
IUnRegister Register<T>(Action<T> onEvent, int priority);
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件监听器
|
||||
/// </summary>
|
||||
|
||||
@ -29,6 +29,24 @@ public interface IIocContainer : IContextAware
|
||||
void RegisterSingleton<TService, TImpl>()
|
||||
where TImpl : class, TService where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册瞬态服务,指定服务类型和实现类型
|
||||
/// 每次解析时都会创建新的实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">服务接口或基类类型</typeparam>
|
||||
/// <typeparam name="TImpl">具体的实现类型</typeparam>
|
||||
void RegisterTransient<TService, TImpl>()
|
||||
where TImpl : class, TService where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册作用域服务,指定服务类型和实现类型
|
||||
/// 在同一作用域内共享实例,不同作用域使用不同实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">服务接口或基类类型</typeparam>
|
||||
/// <typeparam name="TImpl">具体的实现类型</typeparam>
|
||||
void RegisterScoped<TService, TImpl>()
|
||||
where TImpl : class, TService where TService : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册多个实例
|
||||
/// 将实例注册到其实现的所有接口和具体类型上
|
||||
@ -183,5 +201,12 @@ public interface IIocContainer : IContextAware
|
||||
/// <returns>底层的IServiceCollection实例</returns>
|
||||
IServiceCollection GetServicesUnsafe { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的服务作用域
|
||||
/// 作用域内的 Scoped 服务将共享同一实例
|
||||
/// </summary>
|
||||
/// <returns>服务作用域实例</returns>
|
||||
IServiceScope CreateScope();
|
||||
|
||||
#endregion
|
||||
}
|
||||
237
GFramework.Core.Tests/events/EventBusPriorityTests.cs
Normal file
237
GFramework.Core.Tests/events/EventBusPriorityTests.cs
Normal file
@ -0,0 +1,237 @@
|
||||
using GFramework.Core.Abstractions.events;
|
||||
using GFramework.Core.events;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.events;
|
||||
|
||||
/// <summary>
|
||||
/// 测试事件系统优先级和传播控制功能
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class EventBusPriorityTests
|
||||
{
|
||||
private class TestEvent
|
||||
{
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Register_With_Priority_Should_Execute_In_Priority_Order()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionOrder = new List<int>();
|
||||
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(1), priority: 1);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(3), priority: 3);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(2), priority: 2);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionOrder, Is.EqualTo(new[] { 3, 2, 1 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Register_Without_Priority_Should_Use_Default_Priority()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionOrder = new List<string>();
|
||||
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add("default"), priority: 0);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add("high"), priority: 10);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionOrder[0], Is.EqualTo("high"));
|
||||
Assert.That(executionOrder[1], Is.EqualTo("default"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Send_With_Propagation_All_Should_Execute_All_Handlers()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionCount = 0;
|
||||
|
||||
eventBus.Register<TestEvent>(_ => executionCount++, priority: 1);
|
||||
eventBus.Register<TestEvent>(_ => executionCount++, priority: 2);
|
||||
eventBus.Register<TestEvent>(_ => executionCount++, priority: 3);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionCount, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Send_With_Propagation_Highest_Should_Execute_Only_Highest_Priority()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionOrder = new List<int>();
|
||||
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(1), priority: 1);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(3), priority: 3);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(2), priority: 2);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.Highest);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionOrder.Count, Is.EqualTo(1));
|
||||
Assert.That(executionOrder[0], Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Send_With_Propagation_Highest_Should_Execute_All_With_Same_Highest_Priority()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionOrder = new List<string>();
|
||||
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add("high1"), priority: 10);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add("high2"), priority: 10);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add("low"), priority: 1);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.Highest);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionOrder.Count, Is.EqualTo(2));
|
||||
Assert.That(executionOrder, Does.Contain("high1"));
|
||||
Assert.That(executionOrder, Does.Contain("high2"));
|
||||
Assert.That(executionOrder, Does.Not.Contain("low"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Negative_Priority_Should_Work_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionOrder = new List<int>();
|
||||
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(-1), priority: -1);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(0), priority: 0);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add(1), priority: 1);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionOrder, Is.EqualTo(new[] { 1, 0, -1 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Multiple_Events_Should_Maintain_Independent_Priorities()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var event1Order = new List<int>();
|
||||
var event2Order = new List<int>();
|
||||
|
||||
eventBus.Register<TestEvent>(_ => event1Order.Add(1), priority: 1);
|
||||
eventBus.Register<TestEvent>(_ => event1Order.Add(2), priority: 2);
|
||||
|
||||
eventBus.Register<string>(_ => event2Order.Add(10), priority: 10);
|
||||
eventBus.Register<string>(_ => event2Order.Add(20), priority: 20);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All);
|
||||
eventBus.Send("test", EventPropagation.All);
|
||||
|
||||
// Assert
|
||||
Assert.That(event1Order, Is.EqualTo(new[] { 2, 1 }));
|
||||
Assert.That(event2Order, Is.EqualTo(new[] { 20, 10 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UnRegister_Should_Remove_Handler_From_Priority_List()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionCount = 0;
|
||||
Action<TestEvent> handler = _ => executionCount++;
|
||||
|
||||
var unregister = eventBus.Register(handler, priority: 5);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All);
|
||||
unregister.UnRegister();
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Send_Without_Propagation_Should_Use_Default_Event_System()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionCount = 0;
|
||||
|
||||
// 使用默认注册(无优先级)
|
||||
eventBus.Register<TestEvent>(_ => executionCount++);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent()); // 不指定传播模式
|
||||
|
||||
// Assert
|
||||
Assert.That(executionCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Priority_Event_And_Normal_Event_Should_Be_Independent()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var normalCount = 0;
|
||||
var priorityCount = 0;
|
||||
|
||||
eventBus.Register<TestEvent>(_ => normalCount++);
|
||||
eventBus.Register<TestEvent>(_ => priorityCount++, priority: 1);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent()); // 触发普通事件
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All); // 触发优先级事件
|
||||
|
||||
// Assert
|
||||
Assert.That(normalCount, Is.EqualTo(1));
|
||||
Assert.That(priorityCount, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Empty_Event_Bus_Should_Not_Throw_Exception()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
|
||||
// Act & Assert
|
||||
Assert.DoesNotThrow(() => eventBus.Send(new TestEvent(), EventPropagation.All));
|
||||
Assert.DoesNotThrow(() => eventBus.Send(new TestEvent(), EventPropagation.Highest));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Same_Priority_Handlers_Should_Execute_In_Registration_Order()
|
||||
{
|
||||
// Arrange
|
||||
var eventBus = new EventBus();
|
||||
var executionOrder = new List<string>();
|
||||
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add("first"), priority: 5);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add("second"), priority: 5);
|
||||
eventBus.Register<TestEvent>(_ => executionOrder.Add("third"), priority: 5);
|
||||
|
||||
// Act
|
||||
eventBus.Send(new TestEvent(), EventPropagation.All);
|
||||
|
||||
// Assert
|
||||
Assert.That(executionOrder, Is.EqualTo(new[] { "first", "second", "third" }));
|
||||
}
|
||||
}
|
||||
152
GFramework.Core.Tests/extensions/SpanExtensionsTests.cs
Normal file
152
GFramework.Core.Tests/extensions/SpanExtensionsTests.cs
Normal file
@ -0,0 +1,152 @@
|
||||
using GFramework.Core.extensions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 测试 SpanExtensions 扩展方法的功能
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class SpanExtensionsTests
|
||||
{
|
||||
[Test]
|
||||
public void TryParseValue_Should_Parse_Valid_Integer()
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<char> span = "123";
|
||||
|
||||
// Act
|
||||
var success = span.TryParseValue<int>(out var result);
|
||||
|
||||
// Assert
|
||||
Assert.That(success, Is.True);
|
||||
Assert.That(result, Is.EqualTo(123));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParseValue_Should_Fail_For_Invalid_Integer()
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<char> span = "abc";
|
||||
|
||||
// Act
|
||||
var success = span.TryParseValue<int>(out var result);
|
||||
|
||||
// Assert
|
||||
Assert.That(success, Is.False);
|
||||
Assert.That(result, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParseValue_Should_Parse_Valid_Double()
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<char> span = "123.45";
|
||||
|
||||
// Act
|
||||
var success = span.TryParseValue<double>(out var result);
|
||||
|
||||
// Assert
|
||||
Assert.That(success, Is.True);
|
||||
Assert.That(result, Is.EqualTo(123.45).Within(0.001));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParseValue_Should_Parse_Valid_Boolean()
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<char> span = "true";
|
||||
|
||||
// Act
|
||||
var success = span.TryParseValue<bool>(out var result);
|
||||
|
||||
// Assert
|
||||
Assert.That(success, Is.True);
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TryParseValue_Should_Parse_Valid_Guid()
|
||||
{
|
||||
// Arrange
|
||||
var guid = Guid.NewGuid();
|
||||
ReadOnlySpan<char> span = guid.ToString();
|
||||
|
||||
// Act
|
||||
var success = span.TryParseValue<Guid>(out var result);
|
||||
|
||||
// Assert
|
||||
Assert.That(success, Is.True);
|
||||
Assert.That(result, Is.EqualTo(guid));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CountOccurrences_Should_Return_Correct_Count()
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<int> span = stackalloc int[] { 1, 2, 3, 2, 1 };
|
||||
|
||||
// Act
|
||||
var count = span.CountOccurrences(2);
|
||||
|
||||
// Assert
|
||||
Assert.That(count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CountOccurrences_Should_Return_Zero_When_Value_Not_Found()
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<int> span = stackalloc int[] { 1, 2, 3 };
|
||||
|
||||
// Act
|
||||
var count = span.CountOccurrences(5);
|
||||
|
||||
// Assert
|
||||
Assert.That(count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CountOccurrences_Should_Return_Zero_For_Empty_Span()
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<int> span = ReadOnlySpan<int>.Empty;
|
||||
|
||||
// Act
|
||||
var count = span.CountOccurrences(1);
|
||||
|
||||
// Assert
|
||||
Assert.That(count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CountOccurrences_With_Chars_Should_Work()
|
||||
{
|
||||
// Arrange
|
||||
ReadOnlySpan<char> span = "hello";
|
||||
|
||||
// Act
|
||||
var count = span.CountOccurrences('l');
|
||||
|
||||
// Assert
|
||||
Assert.That(count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CountOccurrences_Should_Work_With_Custom_Types()
|
||||
{
|
||||
// Arrange
|
||||
var item1 = new TestItem(1);
|
||||
var item2 = new TestItem(2);
|
||||
var item3 = new TestItem(2);
|
||||
ReadOnlySpan<TestItem> span = new[] { item1, item2, item3 };
|
||||
|
||||
// Act
|
||||
var count = span.CountOccurrences(new TestItem(2));
|
||||
|
||||
// Assert
|
||||
Assert.That(count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
private record TestItem(int Value);
|
||||
}
|
||||
208
GFramework.Core.Tests/ioc/IocContainerLifetimeTests.cs
Normal file
208
GFramework.Core.Tests/ioc/IocContainerLifetimeTests.cs
Normal file
@ -0,0 +1,208 @@
|
||||
using GFramework.Core.ioc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GFramework.Core.Tests.ioc;
|
||||
|
||||
/// <summary>
|
||||
/// 测试 IoC 容器生命周期功能
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class IocContainerLifetimeTests
|
||||
{
|
||||
private interface ITestService
|
||||
{
|
||||
Guid Id { get; }
|
||||
}
|
||||
|
||||
private class TestService : ITestService
|
||||
{
|
||||
public Guid Id { get; } = Guid.NewGuid();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterSingleton_Should_Return_Same_Instance()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterSingleton<ITestService, TestService>();
|
||||
container.Freeze();
|
||||
|
||||
// Act
|
||||
var instance1 = container.Get<ITestService>();
|
||||
var instance2 = container.Get<ITestService>();
|
||||
|
||||
// Assert
|
||||
Assert.That(instance1, Is.Not.Null);
|
||||
Assert.That(instance2, Is.Not.Null);
|
||||
Assert.That(instance1!.Id, Is.EqualTo(instance2!.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterTransient_Should_Return_Different_Instances()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterTransient<ITestService, TestService>();
|
||||
container.Freeze();
|
||||
|
||||
// Act
|
||||
var instance1 = container.Get<ITestService>();
|
||||
var instance2 = container.Get<ITestService>();
|
||||
|
||||
// Assert
|
||||
Assert.That(instance1, Is.Not.Null);
|
||||
Assert.That(instance2, Is.Not.Null);
|
||||
Assert.That(instance1!.Id, Is.Not.EqualTo(instance2!.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterScoped_Should_Return_Same_Instance_Within_Scope()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterScoped<ITestService, TestService>();
|
||||
container.Freeze();
|
||||
|
||||
// Act
|
||||
using var scope = container.CreateScope();
|
||||
var instance1 = scope.ServiceProvider.GetService<ITestService>();
|
||||
var instance2 = scope.ServiceProvider.GetService<ITestService>();
|
||||
|
||||
// Assert
|
||||
Assert.That(instance1, Is.Not.Null);
|
||||
Assert.That(instance2, Is.Not.Null);
|
||||
Assert.That(instance1!.Id, Is.EqualTo(instance2!.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterScoped_Should_Return_Different_Instances_Across_Scopes()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterScoped<ITestService, TestService>();
|
||||
container.Freeze();
|
||||
|
||||
// Act
|
||||
ITestService? instance1;
|
||||
ITestService? instance2;
|
||||
|
||||
using (var scope1 = container.CreateScope())
|
||||
{
|
||||
instance1 = scope1.ServiceProvider.GetService<ITestService>();
|
||||
}
|
||||
|
||||
using (var scope2 = container.CreateScope())
|
||||
{
|
||||
instance2 = scope2.ServiceProvider.GetService<ITestService>();
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.That(instance1, Is.Not.Null);
|
||||
Assert.That(instance2, Is.Not.Null);
|
||||
Assert.That(instance1!.Id, Is.Not.EqualTo(instance2!.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateScope_Should_Throw_When_Container_Not_Frozen()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterScoped<ITestService, TestService>();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(() => container.CreateScope());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterTransient_Should_Throw_When_Container_Is_Frozen()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.Freeze();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
container.RegisterTransient<ITestService, TestService>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterScoped_Should_Throw_When_Container_Is_Frozen()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.Freeze();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
container.RegisterScoped<ITestService, TestService>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Mixed_Lifetimes_Should_Work_Together()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterSingleton<ITestService, TestService>();
|
||||
container.RegisterTransient<ITestService, TestService>();
|
||||
container.RegisterScoped<ITestService, TestService>();
|
||||
container.Freeze();
|
||||
|
||||
// Act
|
||||
var singletonInstances = container.GetAll<ITestService>().ToList();
|
||||
|
||||
// Assert
|
||||
Assert.That(singletonInstances.Count, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Scoped_Service_Should_Be_Disposed_When_Scope_Disposed()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterScoped<ITestService, TestService>();
|
||||
container.Freeze();
|
||||
|
||||
ITestService? instance;
|
||||
using (var scope = container.CreateScope())
|
||||
{
|
||||
instance = scope.ServiceProvider.GetService<ITestService>();
|
||||
Assert.That(instance, Is.Not.Null);
|
||||
}
|
||||
|
||||
// Act & Assert - 作用域已释放,实例应该被清理
|
||||
// 注意:这里只是验证作用域可以正常释放,无法直接验证实例是否被 Dispose
|
||||
Assert.Pass("Scope disposed successfully");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Multiple_Scopes_Can_Be_Created_Concurrently()
|
||||
{
|
||||
// Arrange
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterScoped<ITestService, TestService>();
|
||||
container.Freeze();
|
||||
|
||||
// Act
|
||||
var scope1 = container.CreateScope();
|
||||
var scope2 = container.CreateScope();
|
||||
var scope3 = container.CreateScope();
|
||||
|
||||
var instance1 = scope1.ServiceProvider.GetService<ITestService>();
|
||||
var instance2 = scope2.ServiceProvider.GetService<ITestService>();
|
||||
var instance3 = scope3.ServiceProvider.GetService<ITestService>();
|
||||
|
||||
// Assert
|
||||
Assert.That(instance1, Is.Not.Null);
|
||||
Assert.That(instance2, Is.Not.Null);
|
||||
Assert.That(instance3, Is.Not.Null);
|
||||
Assert.That(instance1!.Id, Is.Not.EqualTo(instance2!.Id));
|
||||
Assert.That(instance2!.Id, Is.Not.EqualTo(instance3!.Id));
|
||||
Assert.That(instance1!.Id, Is.Not.EqualTo(instance3!.Id));
|
||||
|
||||
// Cleanup
|
||||
scope1.Dispose();
|
||||
scope2.Dispose();
|
||||
scope3.Dispose();
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ namespace GFramework.Core.events;
|
||||
public class EventBus : IEventBus
|
||||
{
|
||||
private readonly EasyEvents _mEvents = new();
|
||||
private readonly EasyEvents _mPriorityEvents = new();
|
||||
|
||||
/// <summary>
|
||||
/// 发送事件,自动创建事件实例
|
||||
@ -32,6 +33,19 @@ public class EventBus : IEventBus
|
||||
.Trigger(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送指定的事件实例,并指定传播模式
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="e">事件实例</param>
|
||||
/// <param name="propagation">事件传播模式</param>
|
||||
public void Send<T>(T e, EventPropagation propagation)
|
||||
{
|
||||
_mPriorityEvents
|
||||
.GetOrAddEvent<PriorityEvent<T>>()
|
||||
.Trigger(e, propagation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件监听器
|
||||
/// </summary>
|
||||
@ -43,6 +57,18 @@ public class EventBus : IEventBus
|
||||
return _mEvents.GetOrAddEvent<Event<T>>().Register(onEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件监听器,并指定优先级
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件类型</typeparam>
|
||||
/// <param name="onEvent">事件处理回调函数</param>
|
||||
/// <param name="priority">优先级,数值越大优先级越高</param>
|
||||
/// <returns>反注册接口,用于注销事件监听</returns>
|
||||
public IUnRegister Register<T>(Action<T> onEvent, int priority)
|
||||
{
|
||||
return _mPriorityEvents.GetOrAddEvent<PriorityEvent<T>>().Register(onEvent, priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件监听器
|
||||
/// </summary>
|
||||
|
||||
141
GFramework.Core/events/PriorityEvent.cs
Normal file
141
GFramework.Core/events/PriorityEvent.cs
Normal file
@ -0,0 +1,141 @@
|
||||
using GFramework.Core.Abstractions.events;
|
||||
|
||||
namespace GFramework.Core.events;
|
||||
|
||||
/// <summary>
|
||||
/// 支持优先级的泛型事件类
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件回调函数的参数类型</typeparam>
|
||||
public class PriorityEvent<T> : IEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 事件处理器包装类,包含处理器和优先级
|
||||
/// </summary>
|
||||
private class EventHandler
|
||||
{
|
||||
public Action<T> Handler { get; }
|
||||
public int Priority { get; }
|
||||
|
||||
public EventHandler(Action<T> handler, int priority)
|
||||
{
|
||||
Handler = handler;
|
||||
Priority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存储已注册的事件处理器列表
|
||||
/// </summary>
|
||||
private readonly List<EventHandler> _handlers = new();
|
||||
|
||||
/// <summary>
|
||||
/// 标记事件是否已被处理(用于 UntilHandled 传播模式)
|
||||
/// </summary>
|
||||
private bool _handled;
|
||||
|
||||
/// <summary>
|
||||
/// 显式实现 IEvent 接口中的 Register 方法
|
||||
/// </summary>
|
||||
/// <param name="onEvent">无参事件处理方法</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器</returns>
|
||||
IUnRegister IEvent.Register(Action onEvent)
|
||||
{
|
||||
return Register(_ => onEvent(), 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个事件监听器,默认优先级为 0
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要注册的事件处理方法</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器</returns>
|
||||
public IUnRegister Register(Action<T> onEvent)
|
||||
{
|
||||
return Register(onEvent, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个事件监听器,并指定优先级
|
||||
/// </summary>
|
||||
/// <param name="onEvent">要注册的事件处理方法</param>
|
||||
/// <param name="priority">优先级,数值越大优先级越高</param>
|
||||
/// <returns>IUnRegister 对象,用于稍后注销该事件监听器</returns>
|
||||
public IUnRegister Register(Action<T> onEvent, int priority)
|
||||
{
|
||||
var handler = new EventHandler(onEvent, priority);
|
||||
_handlers.Add(handler);
|
||||
|
||||
// 按优先级降序排序(高优先级在前)
|
||||
_handlers.Sort((a, b) => b.Priority.CompareTo(a.Priority));
|
||||
|
||||
return new DefaultUnRegister(() => UnRegister(onEvent));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消指定的事件监听器
|
||||
/// </summary>
|
||||
/// <param name="onEvent">需要被注销的事件处理方法</param>
|
||||
public void UnRegister(Action<T> onEvent)
|
||||
{
|
||||
_handlers.RemoveAll(h => h.Handler == onEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发所有已注册的事件处理程序(默认传播模式:All)
|
||||
/// </summary>
|
||||
/// <param name="t">传递给事件处理程序的参数</param>
|
||||
public void Trigger(T t)
|
||||
{
|
||||
Trigger(t, EventPropagation.All);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件处理程序,并指定传播模式
|
||||
/// </summary>
|
||||
/// <param name="t">传递给事件处理程序的参数</param>
|
||||
/// <param name="propagation">事件传播模式</param>
|
||||
public void Trigger(T t, EventPropagation propagation)
|
||||
{
|
||||
_handled = false;
|
||||
|
||||
switch (propagation)
|
||||
{
|
||||
case EventPropagation.All:
|
||||
// 触发所有处理器
|
||||
foreach (var handler in _handlers)
|
||||
{
|
||||
handler.Handler.Invoke(t);
|
||||
}
|
||||
break;
|
||||
|
||||
case EventPropagation.UntilHandled:
|
||||
// 触发直到某个处理器标记为已处理
|
||||
foreach (var handler in _handlers)
|
||||
{
|
||||
handler.Handler.Invoke(t);
|
||||
if (_handled) break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EventPropagation.Highest:
|
||||
// 仅触发最高优先级的处理器
|
||||
if (_handlers.Count > 0)
|
||||
{
|
||||
var highestPriority = _handlers[0].Priority;
|
||||
foreach (var handler in _handlers)
|
||||
{
|
||||
if (handler.Priority < highestPriority) break;
|
||||
handler.Handler.Invoke(t);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标记事件为已处理(用于 UntilHandled 传播模式)
|
||||
/// </summary>
|
||||
public void MarkAsHandled()
|
||||
{
|
||||
_handled = true;
|
||||
}
|
||||
}
|
||||
52
GFramework.Core/extensions/SpanExtensions.cs
Normal file
52
GFramework.Core/extensions/SpanExtensions.cs
Normal file
@ -0,0 +1,52 @@
|
||||
namespace GFramework.Core.extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Span 和 ReadOnlySpan 扩展方法,提供零分配的高性能操作
|
||||
/// </summary>
|
||||
public static class SpanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试将字符 span 解析为指定类型
|
||||
/// </summary>
|
||||
/// <typeparam name="T">目标类型,必须实现 ISpanParsable 接口</typeparam>
|
||||
/// <param name="span">要解析的字符 span</param>
|
||||
/// <param name="result">解析结果</param>
|
||||
/// <returns>如果解析成功返回 true,否则返回 false</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// ReadOnlySpan<char> span = "123";
|
||||
/// if (span.TryParseValue<int>(out var result))
|
||||
/// {
|
||||
/// Console.WriteLine(result); // 123
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static bool TryParseValue<T>(this ReadOnlySpan<char> span, out T? result) where T : ISpanParsable<T>
|
||||
{
|
||||
return T.TryParse(span, null, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算 span 中指定值出现的次数
|
||||
/// </summary>
|
||||
/// <typeparam name="T">元素类型,必须实现 IEquatable 接口</typeparam>
|
||||
/// <param name="span">要搜索的 span</param>
|
||||
/// <param name="value">要计数的值</param>
|
||||
/// <returns>值出现的次数</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// ReadOnlySpan<int> span = stackalloc int[] { 1, 2, 3, 2, 1 };
|
||||
/// var count = span.CountOccurrences(2); // 2
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static int CountOccurrences<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T>
|
||||
{
|
||||
var count = 0;
|
||||
foreach (var item in span)
|
||||
{
|
||||
if (item.Equals(value))
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@ -120,6 +120,52 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册瞬态服务,指定服务类型和实现类型
|
||||
/// 每次解析时都会创建新的实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">服务接口或基类类型</typeparam>
|
||||
/// <typeparam name="TImpl">具体的实现类型</typeparam>
|
||||
public void RegisterTransient<TService, TImpl>()
|
||||
where TImpl : class, TService
|
||||
where TService : class
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
ThrowIfFrozen();
|
||||
GetServicesUnsafe.AddTransient<TService, TImpl>();
|
||||
_logger.Debug($"Transient registered: {typeof(TService).Name}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册作用域服务,指定服务类型和实现类型
|
||||
/// 在同一作用域内共享实例,不同作用域使用不同实例
|
||||
/// </summary>
|
||||
/// <typeparam name="TService">服务接口或基类类型</typeparam>
|
||||
/// <typeparam name="TImpl">具体的实现类型</typeparam>
|
||||
public void RegisterScoped<TService, TImpl>()
|
||||
where TImpl : class, TService
|
||||
where TService : class
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
ThrowIfFrozen();
|
||||
GetServicesUnsafe.AddScoped<TService, TImpl>();
|
||||
_logger.Debug($"Scoped registered: {typeof(TService).Name}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册多个实例到其所有接口和具体类型
|
||||
@ -291,7 +337,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// 配置服务
|
||||
/// </summary>
|
||||
/// <param name="configurator">服务配置委托</param>
|
||||
public void ExecuteServicesHook(Action<IServiceCollection>? configurator)
|
||||
public void ExecuteServicesHook(Action<IServiceCollection>? configurator = null)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
@ -652,5 +698,33 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
/// <returns>底层的IServiceCollection实例</returns>
|
||||
public IServiceCollection GetServicesUnsafe { get; } = serviceCollection ?? new ServiceCollection();
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的服务作用域
|
||||
/// 作用域内的 Scoped 服务将共享同一实例
|
||||
/// </summary>
|
||||
/// <returns>服务作用域实例</returns>
|
||||
/// <exception cref="InvalidOperationException">当容器未冻结时抛出</exception>
|
||||
public IServiceScope CreateScope()
|
||||
{
|
||||
if (_provider == null)
|
||||
{
|
||||
const string errorMsg = "Cannot create scope before container is frozen";
|
||||
_logger.Error(errorMsg);
|
||||
throw new InvalidOperationException(errorMsg);
|
||||
}
|
||||
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
var scope = _provider.CreateScope();
|
||||
_logger.Debug("Service scope created");
|
||||
return scope;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user