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