diff --git a/GFramework.Core.Abstractions/events/EventPropagation.cs b/GFramework.Core.Abstractions/events/EventPropagation.cs
new file mode 100644
index 0000000..61e3d86
--- /dev/null
+++ b/GFramework.Core.Abstractions/events/EventPropagation.cs
@@ -0,0 +1,22 @@
+namespace GFramework.Core.Abstractions.events;
+
+///
+/// 事件传播模式
+///
+public enum EventPropagation
+{
+ ///
+ /// 传播到所有处理器
+ ///
+ All,
+
+ ///
+ /// 传播直到某个处理器标记为已处理
+ ///
+ UntilHandled,
+
+ ///
+ /// 仅传播到最高优先级的处理器
+ ///
+ Highest
+}
diff --git a/GFramework.Core.Abstractions/events/IEventBus.cs b/GFramework.Core.Abstractions/events/IEventBus.cs
index e8beddf..25f68e6 100644
--- a/GFramework.Core.Abstractions/events/IEventBus.cs
+++ b/GFramework.Core.Abstractions/events/IEventBus.cs
@@ -18,6 +18,14 @@ public interface IEventBus
/// 事件实例
void Send(T e);
+ ///
+ /// 发送指定的事件实例,并指定传播模式
+ ///
+ /// 事件类型
+ /// 事件实例
+ /// 事件传播模式
+ void Send(T e, EventPropagation propagation);
+
///
/// 注册事件监听器
///
@@ -26,6 +34,15 @@ public interface IEventBus
/// 反注册接口,用于注销事件监听
IUnRegister Register(Action onEvent);
+ ///
+ /// 注册事件监听器,并指定优先级
+ ///
+ /// 事件类型
+ /// 事件处理回调函数
+ /// 优先级,数值越大优先级越高
+ /// 反注册接口,用于注销事件监听
+ IUnRegister Register(Action onEvent, int priority);
+
///
/// 注销事件监听器
///
diff --git a/GFramework.Core.Abstractions/ioc/IIocContainer.cs b/GFramework.Core.Abstractions/ioc/IIocContainer.cs
index 7648a9c..9a07c8d 100644
--- a/GFramework.Core.Abstractions/ioc/IIocContainer.cs
+++ b/GFramework.Core.Abstractions/ioc/IIocContainer.cs
@@ -29,6 +29,24 @@ public interface IIocContainer : IContextAware
void RegisterSingleton()
where TImpl : class, TService where TService : class;
+ ///
+ /// 注册瞬态服务,指定服务类型和实现类型
+ /// 每次解析时都会创建新的实例
+ ///
+ /// 服务接口或基类类型
+ /// 具体的实现类型
+ void RegisterTransient()
+ where TImpl : class, TService where TService : class;
+
+ ///
+ /// 注册作用域服务,指定服务类型和实现类型
+ /// 在同一作用域内共享实例,不同作用域使用不同实例
+ ///
+ /// 服务接口或基类类型
+ /// 具体的实现类型
+ void RegisterScoped()
+ where TImpl : class, TService where TService : class;
+
///
/// 注册多个实例
/// 将实例注册到其实现的所有接口和具体类型上
@@ -183,5 +201,12 @@ public interface IIocContainer : IContextAware
/// 底层的IServiceCollection实例
IServiceCollection GetServicesUnsafe { get; }
+ ///
+ /// 创建一个新的服务作用域
+ /// 作用域内的 Scoped 服务将共享同一实例
+ ///
+ /// 服务作用域实例
+ IServiceScope CreateScope();
+
#endregion
}
\ No newline at end of file
diff --git a/GFramework.Core.Tests/events/EventBusPriorityTests.cs b/GFramework.Core.Tests/events/EventBusPriorityTests.cs
new file mode 100644
index 0000000..9a8a832
--- /dev/null
+++ b/GFramework.Core.Tests/events/EventBusPriorityTests.cs
@@ -0,0 +1,237 @@
+using GFramework.Core.Abstractions.events;
+using GFramework.Core.events;
+using NUnit.Framework;
+
+namespace GFramework.Core.Tests.events;
+
+///
+/// 测试事件系统优先级和传播控制功能
+///
+[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();
+
+ eventBus.Register(_ => executionOrder.Add(1), priority: 1);
+ eventBus.Register(_ => executionOrder.Add(3), priority: 3);
+ eventBus.Register(_ => 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();
+
+ eventBus.Register(_ => executionOrder.Add("default"), priority: 0);
+ eventBus.Register(_ => 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(_ => executionCount++, priority: 1);
+ eventBus.Register(_ => executionCount++, priority: 2);
+ eventBus.Register(_ => 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();
+
+ eventBus.Register(_ => executionOrder.Add(1), priority: 1);
+ eventBus.Register(_ => executionOrder.Add(3), priority: 3);
+ eventBus.Register(_ => 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();
+
+ eventBus.Register(_ => executionOrder.Add("high1"), priority: 10);
+ eventBus.Register(_ => executionOrder.Add("high2"), priority: 10);
+ eventBus.Register(_ => 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();
+
+ eventBus.Register(_ => executionOrder.Add(-1), priority: -1);
+ eventBus.Register(_ => executionOrder.Add(0), priority: 0);
+ eventBus.Register(_ => 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();
+ var event2Order = new List();
+
+ eventBus.Register(_ => event1Order.Add(1), priority: 1);
+ eventBus.Register(_ => event1Order.Add(2), priority: 2);
+
+ eventBus.Register(_ => event2Order.Add(10), priority: 10);
+ eventBus.Register(_ => 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 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(_ => 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(_ => normalCount++);
+ eventBus.Register(_ => 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();
+
+ eventBus.Register(_ => executionOrder.Add("first"), priority: 5);
+ eventBus.Register(_ => executionOrder.Add("second"), priority: 5);
+ eventBus.Register(_ => executionOrder.Add("third"), priority: 5);
+
+ // Act
+ eventBus.Send(new TestEvent(), EventPropagation.All);
+
+ // Assert
+ Assert.That(executionOrder, Is.EqualTo(new[] { "first", "second", "third" }));
+ }
+}
diff --git a/GFramework.Core.Tests/extensions/SpanExtensionsTests.cs b/GFramework.Core.Tests/extensions/SpanExtensionsTests.cs
new file mode 100644
index 0000000..5986d9b
--- /dev/null
+++ b/GFramework.Core.Tests/extensions/SpanExtensionsTests.cs
@@ -0,0 +1,152 @@
+using GFramework.Core.extensions;
+using NUnit.Framework;
+
+namespace GFramework.Core.Tests.extensions;
+
+///
+/// 测试 SpanExtensions 扩展方法的功能
+///
+[TestFixture]
+public class SpanExtensionsTests
+{
+ [Test]
+ public void TryParseValue_Should_Parse_Valid_Integer()
+ {
+ // Arrange
+ ReadOnlySpan span = "123";
+
+ // Act
+ var success = span.TryParseValue(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 span = "abc";
+
+ // Act
+ var success = span.TryParseValue(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 span = "123.45";
+
+ // Act
+ var success = span.TryParseValue(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 span = "true";
+
+ // Act
+ var success = span.TryParseValue(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 span = guid.ToString();
+
+ // Act
+ var success = span.TryParseValue(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 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 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 span = ReadOnlySpan.Empty;
+
+ // Act
+ var count = span.CountOccurrences(1);
+
+ // Assert
+ Assert.That(count, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void CountOccurrences_With_Chars_Should_Work()
+ {
+ // Arrange
+ ReadOnlySpan 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 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);
+}
diff --git a/GFramework.Core.Tests/ioc/IocContainerLifetimeTests.cs b/GFramework.Core.Tests/ioc/IocContainerLifetimeTests.cs
new file mode 100644
index 0000000..1c48121
--- /dev/null
+++ b/GFramework.Core.Tests/ioc/IocContainerLifetimeTests.cs
@@ -0,0 +1,208 @@
+using GFramework.Core.ioc;
+using Microsoft.Extensions.DependencyInjection;
+using NUnit.Framework;
+
+namespace GFramework.Core.Tests.ioc;
+
+///
+/// 测试 IoC 容器生命周期功能
+///
+[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();
+ container.Freeze();
+
+ // Act
+ var instance1 = container.Get();
+ var instance2 = container.Get();
+
+ // 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();
+ container.Freeze();
+
+ // Act
+ var instance1 = container.Get();
+ var instance2 = container.Get();
+
+ // 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();
+ container.Freeze();
+
+ // Act
+ using var scope = container.CreateScope();
+ var instance1 = scope.ServiceProvider.GetService();
+ var instance2 = scope.ServiceProvider.GetService();
+
+ // 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();
+ container.Freeze();
+
+ // Act
+ ITestService? instance1;
+ ITestService? instance2;
+
+ using (var scope1 = container.CreateScope())
+ {
+ instance1 = scope1.ServiceProvider.GetService();
+ }
+
+ using (var scope2 = container.CreateScope())
+ {
+ instance2 = scope2.ServiceProvider.GetService();
+ }
+
+ // 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();
+
+ // Act & Assert
+ Assert.Throws(() => container.CreateScope());
+ }
+
+ [Test]
+ public void RegisterTransient_Should_Throw_When_Container_Is_Frozen()
+ {
+ // Arrange
+ var container = new MicrosoftDiContainer();
+ container.Freeze();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ container.RegisterTransient());
+ }
+
+ [Test]
+ public void RegisterScoped_Should_Throw_When_Container_Is_Frozen()
+ {
+ // Arrange
+ var container = new MicrosoftDiContainer();
+ container.Freeze();
+
+ // Act & Assert
+ Assert.Throws(() =>
+ container.RegisterScoped());
+ }
+
+ [Test]
+ public void Mixed_Lifetimes_Should_Work_Together()
+ {
+ // Arrange
+ var container = new MicrosoftDiContainer();
+ container.RegisterSingleton();
+ container.RegisterTransient();
+ container.RegisterScoped();
+ container.Freeze();
+
+ // Act
+ var singletonInstances = container.GetAll().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();
+ container.Freeze();
+
+ ITestService? instance;
+ using (var scope = container.CreateScope())
+ {
+ instance = scope.ServiceProvider.GetService();
+ 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();
+ container.Freeze();
+
+ // Act
+ var scope1 = container.CreateScope();
+ var scope2 = container.CreateScope();
+ var scope3 = container.CreateScope();
+
+ var instance1 = scope1.ServiceProvider.GetService();
+ var instance2 = scope2.ServiceProvider.GetService();
+ var instance3 = scope3.ServiceProvider.GetService();
+
+ // 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();
+ }
+}
diff --git a/GFramework.Core/events/EventBus.cs b/GFramework.Core/events/EventBus.cs
index f4fef72..6e142ae 100644
--- a/GFramework.Core/events/EventBus.cs
+++ b/GFramework.Core/events/EventBus.cs
@@ -8,6 +8,7 @@ namespace GFramework.Core.events;
public class EventBus : IEventBus
{
private readonly EasyEvents _mEvents = new();
+ private readonly EasyEvents _mPriorityEvents = new();
///
/// 发送事件,自动创建事件实例
@@ -32,6 +33,19 @@ public class EventBus : IEventBus
.Trigger(e);
}
+ ///
+ /// 发送指定的事件实例,并指定传播模式
+ ///
+ /// 事件类型
+ /// 事件实例
+ /// 事件传播模式
+ public void Send(T e, EventPropagation propagation)
+ {
+ _mPriorityEvents
+ .GetOrAddEvent>()
+ .Trigger(e, propagation);
+ }
+
///
/// 注册事件监听器
///
@@ -43,6 +57,18 @@ public class EventBus : IEventBus
return _mEvents.GetOrAddEvent>().Register(onEvent);
}
+ ///
+ /// 注册事件监听器,并指定优先级
+ ///
+ /// 事件类型
+ /// 事件处理回调函数
+ /// 优先级,数值越大优先级越高
+ /// 反注册接口,用于注销事件监听
+ public IUnRegister Register(Action onEvent, int priority)
+ {
+ return _mPriorityEvents.GetOrAddEvent>().Register(onEvent, priority);
+ }
+
///
/// 注销事件监听器
///
diff --git a/GFramework.Core/events/PriorityEvent.cs b/GFramework.Core/events/PriorityEvent.cs
new file mode 100644
index 0000000..3b07503
--- /dev/null
+++ b/GFramework.Core/events/PriorityEvent.cs
@@ -0,0 +1,141 @@
+using GFramework.Core.Abstractions.events;
+
+namespace GFramework.Core.events;
+
+///
+/// 支持优先级的泛型事件类
+///
+/// 事件回调函数的参数类型
+public class PriorityEvent : IEvent
+{
+ ///
+ /// 事件处理器包装类,包含处理器和优先级
+ ///
+ private class EventHandler
+ {
+ public Action Handler { get; }
+ public int Priority { get; }
+
+ public EventHandler(Action handler, int priority)
+ {
+ Handler = handler;
+ Priority = priority;
+ }
+ }
+
+ ///
+ /// 存储已注册的事件处理器列表
+ ///
+ private readonly List _handlers = new();
+
+ ///
+ /// 标记事件是否已被处理(用于 UntilHandled 传播模式)
+ ///
+ private bool _handled;
+
+ ///
+ /// 显式实现 IEvent 接口中的 Register 方法
+ ///
+ /// 无参事件处理方法
+ /// IUnRegister 对象,用于稍后注销该事件监听器
+ IUnRegister IEvent.Register(Action onEvent)
+ {
+ return Register(_ => onEvent(), 0);
+ }
+
+ ///
+ /// 注册一个事件监听器,默认优先级为 0
+ ///
+ /// 要注册的事件处理方法
+ /// IUnRegister 对象,用于稍后注销该事件监听器
+ public IUnRegister Register(Action onEvent)
+ {
+ return Register(onEvent, 0);
+ }
+
+ ///
+ /// 注册一个事件监听器,并指定优先级
+ ///
+ /// 要注册的事件处理方法
+ /// 优先级,数值越大优先级越高
+ /// IUnRegister 对象,用于稍后注销该事件监听器
+ public IUnRegister Register(Action 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));
+ }
+
+ ///
+ /// 取消指定的事件监听器
+ ///
+ /// 需要被注销的事件处理方法
+ public void UnRegister(Action onEvent)
+ {
+ _handlers.RemoveAll(h => h.Handler == onEvent);
+ }
+
+ ///
+ /// 触发所有已注册的事件处理程序(默认传播模式:All)
+ ///
+ /// 传递给事件处理程序的参数
+ public void Trigger(T t)
+ {
+ Trigger(t, EventPropagation.All);
+ }
+
+ ///
+ /// 触发事件处理程序,并指定传播模式
+ ///
+ /// 传递给事件处理程序的参数
+ /// 事件传播模式
+ 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;
+ }
+ }
+
+ ///
+ /// 标记事件为已处理(用于 UntilHandled 传播模式)
+ ///
+ public void MarkAsHandled()
+ {
+ _handled = true;
+ }
+}
diff --git a/GFramework.Core/extensions/SpanExtensions.cs b/GFramework.Core/extensions/SpanExtensions.cs
new file mode 100644
index 0000000..c5890b7
--- /dev/null
+++ b/GFramework.Core/extensions/SpanExtensions.cs
@@ -0,0 +1,52 @@
+namespace GFramework.Core.extensions;
+
+///
+/// Span 和 ReadOnlySpan 扩展方法,提供零分配的高性能操作
+///
+public static class SpanExtensions
+{
+ ///
+ /// 尝试将字符 span 解析为指定类型
+ ///
+ /// 目标类型,必须实现 ISpanParsable 接口
+ /// 要解析的字符 span
+ /// 解析结果
+ /// 如果解析成功返回 true,否则返回 false
+ ///
+ ///
+ /// ReadOnlySpan<char> span = "123";
+ /// if (span.TryParseValue<int>(out var result))
+ /// {
+ /// Console.WriteLine(result); // 123
+ /// }
+ ///
+ ///
+ public static bool TryParseValue(this ReadOnlySpan span, out T? result) where T : ISpanParsable
+ {
+ return T.TryParse(span, null, out result);
+ }
+
+ ///
+ /// 计算 span 中指定值出现的次数
+ ///
+ /// 元素类型,必须实现 IEquatable 接口
+ /// 要搜索的 span
+ /// 要计数的值
+ /// 值出现的次数
+ ///
+ ///
+ /// ReadOnlySpan<int> span = stackalloc int[] { 1, 2, 3, 2, 1 };
+ /// var count = span.CountOccurrences(2); // 2
+ ///
+ ///
+ public static int CountOccurrences(this ReadOnlySpan span, T value) where T : IEquatable
+ {
+ var count = 0;
+ foreach (var item in span)
+ {
+ if (item.Equals(value))
+ count++;
+ }
+ return count;
+ }
+}
diff --git a/GFramework.Core/ioc/MicrosoftDiContainer.cs b/GFramework.Core/ioc/MicrosoftDiContainer.cs
index c2f36ac..f7eda5a 100644
--- a/GFramework.Core/ioc/MicrosoftDiContainer.cs
+++ b/GFramework.Core/ioc/MicrosoftDiContainer.cs
@@ -120,6 +120,52 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
}
}
+ ///
+ /// 注册瞬态服务,指定服务类型和实现类型
+ /// 每次解析时都会创建新的实例
+ ///
+ /// 服务接口或基类类型
+ /// 具体的实现类型
+ public void RegisterTransient()
+ where TImpl : class, TService
+ where TService : class
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ ThrowIfFrozen();
+ GetServicesUnsafe.AddTransient();
+ _logger.Debug($"Transient registered: {typeof(TService).Name}");
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ }
+
+ ///
+ /// 注册作用域服务,指定服务类型和实现类型
+ /// 在同一作用域内共享实例,不同作用域使用不同实例
+ ///
+ /// 服务接口或基类类型
+ /// 具体的实现类型
+ public void RegisterScoped()
+ where TImpl : class, TService
+ where TService : class
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ ThrowIfFrozen();
+ GetServicesUnsafe.AddScoped();
+ _logger.Debug($"Scoped registered: {typeof(TService).Name}");
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ }
+
///
/// 注册多个实例到其所有接口和具体类型
@@ -291,7 +337,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 配置服务
///
/// 服务配置委托
- public void ExecuteServicesHook(Action? configurator)
+ public void ExecuteServicesHook(Action? configurator = null)
{
_lock.EnterWriteLock();
try
@@ -652,5 +698,33 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
/// 底层的IServiceCollection实例
public IServiceCollection GetServicesUnsafe { get; } = serviceCollection ?? new ServiceCollection();
+ ///
+ /// 创建一个新的服务作用域
+ /// 作用域内的 Scoped 服务将共享同一实例
+ ///
+ /// 服务作用域实例
+ /// 当容器未冻结时抛出
+ 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
}
\ No newline at end of file