Skip to content

Commit

Permalink
Field Injection
Browse files Browse the repository at this point in the history
  • Loading branch information
mewlist committed Feb 21, 2024
1 parent b148ec7 commit 166bc6d
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 1 deletion.
17 changes: 17 additions & 0 deletions Runtime/DIContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class DIContainer : IReadOnlyDIContainer, IAsyncDisposable
{
private static Dictionary<Type, TargetMethodsInfo> MethodInfoMap { get; } = new();
private static Dictionary<Type, TargetPropertiesInfo> PropertyInfoMap { get; } = new();
private static Dictionary<Type, TargetFieldsInfo> FieldInfoMap { get; } = new();

private IReadOnlyDIContainer Parent { get; set; }
private Scene Scene { get; set; }
Expand All @@ -35,6 +36,7 @@ public class DIContainer : IReadOnlyDIContainer, IAsyncDisposable
private ConstructorInjector ConstructorInjector { get; }
private MethodInjector MethodInjector { get; }
private PropertyInjector PropertyInjector { get; }
private FieldInjector FieldInjector { get; }

public IReadOnlyDictionary<TargetTypeInfo, IInternalResolver> ReadOnlyBindings => Resolvers;
internal IReadOnlyDictionary<TargetTypeInfo, ConcurrentObjectBag> ReadOnlyInstanceMap => ResolvedInstanceBag.ReadOnlyInstanceMap;
Expand All @@ -51,6 +53,7 @@ public DIContainer(IReadOnlyDIContainer parent = null, Scene scene = default)
ConstructorInjector = new ConstructorInjector(this);
MethodInjector = new MethodInjector(this);
PropertyInjector = new PropertyInjector(this);
FieldInjector = new FieldInjector(this);
BindFromInstance<IReadOnlyDIContainer, DIContainer>(this);
}

Expand Down Expand Up @@ -185,11 +188,13 @@ private async ValueTask<object> InstantiateInternalAsync(Type targetType, object
var target = await ConstructorInjector.DoInject(targetType, args, scopedInstances);
var targetMethodsInfo = GetTargetMethodInfo(targetType);
var targetPropertiesInfo = GetTargetPropertyInfo(targetType);
var targetFieldsInfo = GetTargetFieldInfo(targetType);

try
{
await MethodInjector.DoInject(target, targetMethodsInfo, args, scopedInstances);
await PropertyInjector.DoInject(target, targetPropertiesInfo);
await FieldInjector.DoInject(target, targetFieldsInfo);
}
catch (Exception e)
{
Expand Down Expand Up @@ -272,11 +277,13 @@ private async ValueTask InjectIntoInternalAsync<T>(T target, object[] args, Scop
var targetType = target.GetType();
var targetMethodsInfo = GetTargetMethodInfo(targetType);
var targetPropertiesInfo = GetTargetPropertyInfo(targetType);
var targetFieldsInfo = GetTargetFieldInfo(targetType);

try
{
await MethodInjector.DoInject(target, targetMethodsInfo, args, scopedInstances);
await PropertyInjector.DoInject(target, targetPropertiesInfo);
await FieldInjector.DoInject(target, targetFieldsInfo);
}
catch (Exception e)
{
Expand Down Expand Up @@ -414,6 +421,16 @@ private TargetPropertiesInfo GetTargetPropertyInfo(Type targetType)
return targetPropertiesInfo;
}

private TargetFieldsInfo GetTargetFieldInfo(Type targetType)
{
if (FieldInfoMap.TryGetValue(targetType, out var targetFieldsInfo))
return targetFieldsInfo;

targetFieldsInfo = new TargetFieldsInfo(targetType);
FieldInfoMap[targetType] = targetFieldsInfo;
return targetFieldsInfo;
}

public async ValueTask DisposeAsync()
{
if (CancellationTokenSource.IsCancellationRequested) return;
Expand Down
36 changes: 36 additions & 0 deletions Runtime/Injector/FieldInjector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Threading.Tasks;

namespace Doinject
{
internal class FieldInjector
{
private DIContainer Container { get; }
private ParameterBuilder ParameterBuilder { get; }

public FieldInjector(DIContainer container)
{
Container = container;
ParameterBuilder = container.ParameterBuilder;
}

public async ValueTask DoInject<T>(
T target,
TargetFieldsInfo fields)
{
if (!fields.Any()) return;
var targetType = target.GetType();
var resolverType = targetType;

if (targetType == typeof(IInjectableComponent))
resolverType = target.GetType();

Container.MarkInjected(resolverType);

foreach (var fieldInfo in fields.InjectFields)
{
var instance = await Container.ResolveAsync(fieldInfo.FieldType);
fieldInfo.SetValue(target, instance);
}
}
}
}
3 changes: 3 additions & 0 deletions Runtime/Injector/FieldInjector.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions Runtime/TargetFieldsInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Doinject
{
internal class TargetFieldsInfo
{
public List<FieldInfo> InjectFields { get; } = new();

public TargetFieldsInfo(Type targetType)
{
foreach (var fieldInfo in targetType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
if (fieldInfo.GetCustomAttributes(typeof(InjectAttribute), true).Length <= 0)
continue;

if (!fieldInfo.IsPublic)
throw new Exception($"Field must be public. {targetType.Name}.{fieldInfo.Name}");
InjectFields.Add(fieldInfo);
}
}

public bool Any()
=> InjectFields.Count > 0;
}
}
3 changes: 3 additions & 0 deletions Runtime/TargetFieldsInfo.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Tests/Components/FieldInjectionComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using UnityEngine;

namespace Doinject.Tests
{
public class FieldInjectionComponent : MonoBehaviour, IInjectableComponent
{
[Inject] public InjectedObject injectedObject;
}
}
3 changes: 3 additions & 0 deletions Tests/Components/FieldInjectionComponent.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 34 additions & 1 deletion Tests/InjectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,43 @@ public async Task PropertyInjectionWithNonPublicSetterComponentTest()
{
await container.ResolveAsync<PropertyInjectionWithNonPublicSetterComponent>();
}
catch (Exception e)
catch (Exception _)
{
Assert.Pass();
}
Assert.Fail();
}

[Test]
public async Task FieldInjectionTest()
{
container.BindTransient<FieldInjectionObject>();
container.BindTransient<InjectedObject>();
var instance = await container.ResolveAsync<FieldInjectionObject>();
Assert.NotNull(instance.injectedObject);
}

[Test]
public async Task FieldInjectionComponentTest()
{
container.BindTransient<FieldInjectionComponent>();
container.BindTransient<InjectedObject>();
var instance = await container.ResolveAsync<FieldInjectionComponent>();
Assert.NotNull(instance.injectedObject);
}

[Test]
public async Task FieldInjectionWithNonPublicSetterComponentTest()
{
container.BindTransient<FieldInjectionWithNonPublicObject>();
container.BindTransient<InjectedObject>();
try
{
await container.ResolveAsync<FieldInjectionWithNonPublicObject>();
}
catch (Exception _)
{
Assert.Pass();
}
Assert.Fail();
}
Expand Down
10 changes: 10 additions & 0 deletions Tests/TestObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ public class PropertyInjectionObject
[Inject] public InjectedObject InjectedObject { get; set; }
}

public class FieldInjectionObject
{
[Inject] public InjectedObject injectedObject;
}

public class FieldInjectionWithNonPublicObject
{
[Inject] private InjectedObject injectedObject;
}

public class InjectableObject : IDisposable
{
public InjectedObject InjectedObject { get; set; }
Expand Down

0 comments on commit 166bc6d

Please sign in to comment.