Skip to content

Commit 8960544

Browse files
docs: Update docs
Co-authored-by: Copilot <copilot@github.com>
1 parent aa3a816 commit 8960544

1 file changed

Lines changed: 184 additions & 2 deletions

File tree

docs/Ioc/12_Container.md

Lines changed: 184 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ Configure generation with `IocContainerAttribute` properties:
128128
> The container generated by `SourceGen.Ioc` does not resolve `IServiceCollection` by itself. If you want to integrate with `IServiceCollection`, you must ensure that the generated container should be constructed with an external `IServiceProvider`.
129129
130130
> [!TIP]
131-
> For most .NET applications, the simplest way to integrate the generated container is to use it with `MS.E.DI`. Since the container implements `IServiceProviderFactory<IServiceCollection>`, you can call `new AppContainer(null).CreateServiceProvider(services)` to build a container that uses `MS.E.DI` as the fallback `IServiceProvider`.
131+
> For most .NET applications, the simplest way to integrate the generated container is to use it with `MS.E.DI`. Since the container implements `IServiceProviderFactory<IServiceCollection>`, you can call `new AppContainer().CreateServiceProvider(services)` to build a container that uses `MS.E.DI` as the fallback `IServiceProvider`.
132132
133133
## Filtering by Tags
134134

@@ -496,14 +496,196 @@ This ensures that cached `ObjectFactory` instances and property activator delega
496496
> The hot reload handler is only generated when at least one of `IComponentActivator` or `IComponentPropertyActivator` is implemented.
497497
> `ClearCache` only clears the caches that actually exist based on which interfaces are implemented.
498498
499+
## Partial Accessors (Fast-Path Service Resolution)
500+
501+
Declare **partial methods** or **partial properties** in the `[IocContainer]` class to get strongly-typed, fast-path accessors for registered services. The generator implements them automatically — no dictionary lookup, no boxing.
502+
503+
### Supported Patterns
504+
505+
|Pattern|Declaration|Generated Implementation|
506+
|:---|:---|:---|
507+
|Partial method|`public partial IMyService GetMyService();`|Calls the internal resolver directly|
508+
|Partial property|`public partial IMyService MyService { get; }`|Calls the internal resolver directly|
509+
|Nullable (optional)|`public partial IMyService? TryGetMyService();`|Returns `null` if unresolvable|
510+
|Keyed service|`[IocInject("key")] public partial IMyService GetMyService();`|Resolves with the specified key|
511+
512+
### Basic Partial Method
513+
514+
```csharp
515+
public interface IMyService { }
516+
517+
[IocRegister<IMyService>(ServiceLifetime.Singleton)]
518+
public class MyService : IMyService { }
519+
520+
[IocContainer]
521+
public partial class AppContainer
522+
{
523+
public partial IMyService GetMyService();
524+
}
525+
```
526+
527+
<details>
528+
<summary>Generated Code</summary>
529+
530+
```csharp
531+
// <auto-generated/>
532+
partial class AppContainer
533+
{
534+
// Internal resolver
535+
private global::TestNamespace.MyService _testNamespace_MyService = null!;
536+
private global::TestNamespace.MyService GetTestNamespace_MyService()
537+
{
538+
if(_testNamespace_MyService is not null) return _testNamespace_MyService;
539+
540+
var instance = new global::TestNamespace.MyService();
541+
542+
_testNamespace_MyService = instance;
543+
return instance;
544+
}
545+
546+
// Partial accessor — direct call, no dictionary lookup
547+
public partial global::TestNamespace.IMyService GetMyService() => GetTestNamespace_MyService();
548+
}
549+
```
550+
551+
</details>
552+
553+
### Partial Property
554+
555+
```csharp
556+
[IocContainer]
557+
public partial class AppContainer
558+
{
559+
public partial IMyService MyService { get; }
560+
}
561+
```
562+
563+
<details>
564+
<summary>Generated Code</summary>
565+
566+
```csharp
567+
// <auto-generated/>
568+
partial class AppContainer
569+
{
570+
public partial global::TestNamespace.IMyService MyService { get => GetTestNamespace_MyService(); }
571+
}
572+
```
573+
574+
</details>
575+
576+
### Nullable (Optional) Accessor
577+
578+
When the return type is nullable, the accessor falls back to the container's `GetService` and returns `null` if the service is not registered — it never throws.
579+
580+
```csharp
581+
[IocContainer]
582+
public partial class AppContainer
583+
{
584+
public partial IMyService? TryGetMyService();
585+
}
586+
```
587+
588+
<details>
589+
<summary>Generated Code</summary>
590+
591+
```csharp
592+
// <auto-generated/>
593+
partial class AppContainer
594+
{
595+
public partial global::TestNamespace.IMyService? TryGetMyService()
596+
=> GetService(typeof(global::TestNamespace.IMyService)) as global::TestNamespace.IMyService;
597+
}
598+
```
599+
600+
</details>
601+
602+
### Keyed Service Accessor
603+
604+
Add `[IocInject("key")]` to resolve a keyed registration:
605+
606+
```csharp
607+
public interface ICache { }
608+
609+
[IocRegister<ICache>(ServiceLifetime.Singleton, Key = "redis")]
610+
public class RedisCache : ICache { }
611+
612+
[IocContainer]
613+
public partial class AppContainer
614+
{
615+
[IocInject("redis")]
616+
public partial ICache GetRedisCache();
617+
}
618+
```
619+
620+
<details>
621+
<summary>Generated Code</summary>
622+
623+
```csharp
624+
// <auto-generated/>
625+
partial class AppContainer
626+
{
627+
public partial global::TestNamespace.ICache GetRedisCache() => GetTestNamespace_RedisCache__redis_();
628+
}
629+
```
630+
631+
</details>
632+
633+
### Resolution Rules
634+
635+
1. The return type is matched against registered service types (fully qualified name).
636+
2. If `[IocInject]` is present, its `Key` is used for keyed service resolution.
637+
3. If a matching internal resolver exists, it is called directly (fast-path).
638+
4. If no internal resolver exists but `IntegrateServiceProvider = true`, fallback to `GetService` / `GetRequiredService`.
639+
5. If no resolver exists and `IntegrateServiceProvider = false`:
640+
- **Nullable** return type → returns `default`
641+
- **Non-nullable** return type → `throw new InvalidOperationException(…)` (and reports `SGIOC021`)
642+
643+
### Naming Conflict Handling
644+
645+
If a user-declared partial method name collides with an internally generated resolver name (e.g., both named `GetMyService`), the generator automatically renames the internal resolver with a `_Resolve` suffix:
646+
647+
```csharp
648+
// User declares:
649+
public partial IMyService GetMyService();
650+
651+
// Generator renames internal resolver to avoid conflict:
652+
// GetMyService → GetMyService_Resolve
653+
```
654+
655+
<details>
656+
<summary>Generated Code</summary>
657+
658+
```csharp
659+
// <auto-generated/>
660+
partial class TestContainer
661+
{
662+
// Internal resolver — renamed to avoid conflict
663+
private global::MyService _myService = null!;
664+
private global::MyService GetMyService_Resolve()
665+
{
666+
if(_myService is not null) return _myService;
667+
668+
var instance = new global::MyService();
669+
670+
_myService = instance;
671+
return instance;
672+
}
673+
674+
// Partial accessor calls the renamed resolver
675+
public partial global::IMyService GetMyService() => GetMyService_Resolve();
676+
}
677+
```
678+
679+
</details>
680+
499681
## Diagnostics
500682

501683
|ID|Severity|Description|
502684
|:---|:---|:---|
503685
|SGIOC018|Error|Container cannot resolve a dependency when `IntegrateServiceProvider = false`.|
504686
|SGIOC019|Error|Container class must be `partial` and cannot be `static`.|
505687
|SGIOC020|Warning|`UseSwitchStatement = true` is ignored when importing modules.|
506-
|SGIOC021|Error|Partial accessor return type cannot be resolved when `IntegrateServiceProvider = false`.|
688+
|SGIOC021|Error|Non-nullable partial accessor return type is not a registered service when `IntegrateServiceProvider = false`.|
507689

508690
---
509691

0 commit comments

Comments
 (0)