Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix collection resolve on parent #321

Merged
merged 9 commits into from
Nov 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions VContainer/Assets/VContainer/Runtime/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,20 @@ public interface IObjectResolver : IDisposable
{
DiagnosticsCollector Diagnostics { get; set; }

/// <summary>
/// Resolve from type
/// </summary>
/// <remarks>
/// This version of resolve looks for all of scopes
/// </remarks>
object Resolve(Type type);

/// <summary>
/// Resolve from meta with registration
/// </summary>
/// <remarks>
/// This version of resolve will look for instances from only the registration information already founds.
/// </remarks>
object Resolve(IRegistration registration);
IScopedObjectResolver CreateScope(Action<IContainerBuilder> installation = null);
void Inject(object instance);
Expand Down Expand Up @@ -49,7 +62,6 @@ internal ScopedContainer(
Root = root;
Parent = parent;
this.registry = registry;

createInstance = registration =>
{
return new Lazy<object>(() => registration.SpawnInstance(this));
Expand Down Expand Up @@ -135,14 +147,31 @@ object CreateTrackedInstance(IRegistration registration)
IRegistration FindRegistration(Type type)
{
IScopedObjectResolver scope = this;
CollectionRegistration entirelyCollection = null;

while (scope != null)
{
if (scope.TryGetRegistration(type, out var registration))
{
return registration;
switch (registration)
{
case CollectionRegistration localCollection:
if (entirelyCollection == null)
entirelyCollection = localCollection;
else
entirelyCollection.Merge(localCollection);
break;
default:
return registration;
}
}
scope = scope.Parent;
}

if (entirelyCollection != null)
{
return entirelyCollection;
}
throw new VContainerException(type, $"No such registration of type: {type}");
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace VContainer.Internal
{
sealed class CollectionRegistration : IRegistration, IEnumerable<IRegistration>
{
public struct Enumerator : IEnumerator<IRegistration>
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimization for CollectionRegistration.GetEnumerator()

{
public IRegistration Current => listEnumerator.Current;
object IEnumerator.Current => Current;

List<IRegistration>.Enumerator listEnumerator;

public Enumerator(CollectionRegistration owner)
{
listEnumerator = owner.registrations.GetEnumerator();
}

public bool MoveNext() => listEnumerator.MoveNext();
public void Dispose() => listEnumerator.Dispose();
void IEnumerator.Reset() => ((IEnumerator)listEnumerator).Reset();
}

public static bool Match(Type openGenericType) => openGenericType == typeof(IEnumerable<>) ||
openGenericType == typeof(IReadOnlyList<>);

public Enumerator GetEnumerator() => new Enumerator(this);
IEnumerator<IRegistration> IEnumerable<IRegistration>.GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public Type ImplementationType { get; }
public IReadOnlyList<Type> InterfaceTypes => interfaceTypes;
public Lifetime Lifetime => Lifetime.Transient; // Collection reference is transient. So its members can have each lifetimes.

readonly Type elementType;

readonly List<Type> interfaceTypes;
readonly List<IRegistration> registrations = new List<IRegistration>();

public CollectionRegistration(Type elementType)
{
this.elementType = elementType;
ImplementationType = elementType.MakeArrayType();
interfaceTypes = new List<Type>
{
RuntimeTypeCache.EnumerableTypeOf(elementType),
RuntimeTypeCache.ReadOnlyListTypeOf(elementType),
};
}

public override string ToString()
{
var contractTypes = InterfaceTypes != null ? string.Join(", ", InterfaceTypes) : "";
return $"CollectionRegistration {ImplementationType} ContractTypes=[{contractTypes}] {Lifetime}";
}

public void Add(IRegistration registration)
{
foreach (var x in registrations)
{
if (x.Lifetime == Lifetime.Singleton && x.ImplementationType == registration.ImplementationType)
{
throw new VContainerException(registration.ImplementationType, $"Conflict implementation type : {registration}");
}
}
registrations.Add(registration);
}

public void Merge(CollectionRegistration other)
{
foreach (var x in other.registrations)
{
Add(x);
}
}

public object SpawnInstance(IObjectResolver resolver)
{
var array = Array.CreateInstance(elementType, registrations.Count);
for (var i = 0; i < registrations.Count; i++)
{
array.SetValue(resolver.Resolve(registrations[i]), i);
}
return array;
}
}
}

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

12 changes: 12 additions & 0 deletions VContainer/Assets/VContainer/Runtime/Internal/ContainerLocal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace VContainer.Internal
{
public sealed class ContainerLocal<T>
{
public readonly T Value;

public ContainerLocal(T value)
{
Value = value;
}
}
}

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;

namespace VContainer.Internal
{
public sealed class ContainerLocalRegistration : IRegistration
{
public Type ImplementationType { get; }
public IReadOnlyList<Type> InterfaceTypes { get; }
public Lifetime Lifetime { get; }

readonly IRegistration valueRegistration;

public ContainerLocalRegistration(Type implementationType, IRegistration valueRegistration)
{
ImplementationType = implementationType;
Lifetime = valueRegistration.Lifetime;
this.valueRegistration = valueRegistration;
}

public object SpawnInstance(IObjectResolver resolver)
{
var value = resolver.Resolve(valueRegistration);
var parameterValues = CappedArrayPool<object>.Shared8Limit.Rent(1);
try
{
parameterValues[0] = value;
return Activator.CreateInstance(ImplementationType, parameterValues);
}
finally
{
CappedArrayPool<object>.Shared8Limit.Return(parameterValues);
}
}
}
}

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

Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,14 @@ static void AddToBuildBuffer(IDictionary<Type, IRegistration> buf, Type service,
{
if (buf.TryGetValue(service, out var exists))
{
var collectionService = typeof(IEnumerable<>).MakeGenericType(service);
CollectionRegistration collection;
if (buf.TryGetValue(collectionService, out var found))
if (buf.TryGetValue(RuntimeTypeCache.EnumerableTypeOf(service), out var found))
{
collection = (CollectionRegistration)found;
}
else
{
collection = new CollectionRegistration(collectionService, service) { exists };
collection = new CollectionRegistration(service) { exists };
AddCollectionToBuildBuffer(buf, collection);
}
collection.Add(registration);
Expand Down Expand Up @@ -99,30 +98,54 @@ public bool TryGet(Type interfaceType, out IRegistration registration)

if (interfaceType.IsConstructedGenericType)
{
var genericType = interfaceType.GetGenericTypeDefinition();
return TryFallbackSingleCollection(interfaceType, genericType, out registration);
var openGenericType = RuntimeTypeCache.OpenGenericTypeOf(interfaceType);
var typeParameters = RuntimeTypeCache.GenericTypeParametersOf(interfaceType);
return TryFallbackToSingleElementCollection(interfaceType, openGenericType, typeParameters, out registration) ||
TryFallbackToContainerLocal(interfaceType, openGenericType, typeParameters, out registration);
}
return false;
}

public bool Exists(Type type) => hashTable.TryGet(type, out _);

bool TryFallbackSingleCollection(Type interfaceType, Type genericType, out IRegistration registration)
bool TryFallbackToContainerLocal(
Type closedGenericType,
Type openGenericType,
IReadOnlyList<Type> typeParameters,
out IRegistration newRegistration)
{
if (genericType == typeof(IEnumerable<>) ||
genericType == typeof(IReadOnlyList<>))
if (openGenericType == typeof(ContainerLocal<>))
{
var elementType = interfaceType.GetGenericArguments()[0];
var valueType = typeParameters[0];
if (TryGet(valueType, out var valueRegistration))
{
newRegistration = new ContainerLocalRegistration(closedGenericType, valueRegistration);
return true;
}
}
newRegistration = null;
return false;
}

bool TryFallbackToSingleElementCollection(
Type closedGenericType,
Type openGenericType,
IReadOnlyList<Type> typeParameters,
out IRegistration newRegistration)
{
if (CollectionRegistration.Match(openGenericType))
{
var elementType = typeParameters[0];
var collectionRegistration = new CollectionRegistration(elementType);
// ReSharper disable once InconsistentlySynchronizedField
if (hashTable.TryGet(elementType, out var elementRegistration) && elementRegistration != null)
{
collectionRegistration.Add(elementRegistration);
}
registration = collectionRegistration;
newRegistration = collectionRegistration;
return true;
}
registration = null;
newRegistration = null;
return false;
}
}
Expand Down
71 changes: 0 additions & 71 deletions VContainer/Assets/VContainer/Runtime/Internal/Registration.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace VContainer.Internal
Expand Down Expand Up @@ -38,76 +37,6 @@ public object SpawnInstance(IObjectResolver resolver)
=> injector.CreateInstance(resolver, parameters);
}

sealed class CollectionRegistration : IRegistration, IEnumerable<IRegistration>
{
public Type ImplementationType { get; }
public IReadOnlyList<Type> InterfaceTypes => interfaceTypes;
public Lifetime Lifetime => Lifetime.Transient; // Collection refernce is transient. Members can have each lifetimes.

readonly Type elementType;

readonly List<Type> interfaceTypes;
readonly IList<IRegistration> registrations = new List<IRegistration>();

public CollectionRegistration(Type elementType) : this(
typeof(List<>).MakeGenericType(elementType),
elementType)
{
}

public CollectionRegistration(Type listType, Type elementType)
{
this.elementType = elementType;
ImplementationType = listType;
interfaceTypes = new List<Type>
{
typeof(IEnumerable<>).MakeGenericType(elementType),
typeof(IReadOnlyList<>).MakeGenericType(elementType),
};
}

public override string ToString()
{
var contractTypes = InterfaceTypes != null ? string.Join(", ", InterfaceTypes) : "";
return $"CollectionRegistration {ImplementationType} ContractTypes=[{contractTypes}] {Lifetime}";
}

public void Add(IRegistration registration)
{
foreach (var x in registrations)
{
if (x.Lifetime == Lifetime.Singleton && x.ImplementationType == registration.ImplementationType)
{
throw new VContainerException(registration.ImplementationType, $"Conflict implementation type : {registration}");
}
}
registrations.Add(registration);
}

public object SpawnInstance(IObjectResolver resolver)
{
var genericType = typeof(List<>).MakeGenericType(elementType);
var parameterValues = CappedArrayPool<object>.Shared8Limit.Rent(1);
parameterValues[0] = registrations.Count;
var list = (IList)Activator.CreateInstance(genericType, parameterValues);
try
{
foreach (var registration in registrations)
{
list.Add(resolver.Resolve(registration));
}
}
finally
{
CappedArrayPool<object>.Shared8Limit.Return(parameterValues);
}
return list;
}

public IEnumerator<IRegistration> GetEnumerator() => registrations.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

sealed class ContainerRegistration : IRegistration
{
public static readonly ContainerRegistration Default = new ContainerRegistration();
Expand Down
Loading