Skip to content
This repository has been archived by the owner on Oct 20, 2023. It is now read-only.

Commit

Permalink
Merge pull request #122 from Barsonax/feature/morecollectionssupport
Browse files Browse the repository at this point in the history
Added support for arrays, lists and hashsets
  • Loading branch information
Barsonax committed Dec 14, 2019
2 parents a6045ea + 4cff708 commit 20fdbab
Show file tree
Hide file tree
Showing 13 changed files with 263 additions and 75 deletions.
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -13,7 +13,13 @@
- Collection support:
1. `IEnumerable<T>`
1. `IReadOnlyCollection<T>`
1. `IReadOnlyList`
1. `IReadOnlyList<T>`
1. `T[]`
1. `List<T>`
1. `ICollection<T>`
1. `IList<T>`
1. `HashSet<T>`
1. `ISet<T>`
- Supports open generics.
- Supports resolving unregistered concrete types.
- Supports decorators.
Expand Down
14 changes: 7 additions & 7 deletions src/Singularity/Collections/InstanceFactoryList.cs
Expand Up @@ -6,20 +6,20 @@ namespace Singularity.Collections
{
internal sealed class InstanceFactoryList<T> : IReadOnlyList<T>
{
private readonly Func<Scoped, object>[] _instanceFactories;
private readonly Func<Scoped, T>[] _instanceFactories;
private readonly Scoped _scope;

public InstanceFactoryList(Scoped scope, Func<Scoped, object>[] instanceFactories)
public InstanceFactoryList(Scoped scope, Func<Scoped, T>[] instanceFactories)
{
_instanceFactories = instanceFactories ?? throw new ArgumentNullException(nameof(instanceFactories));
_scope = scope ?? throw new ArgumentNullException(nameof(scope));
_instanceFactories = instanceFactories;
_scope = scope;
}

public IEnumerator<T> GetEnumerator()
{
foreach (Func<Scoped, object> instanceFactory in _instanceFactories)
foreach (Func<Scoped, T> instanceFactory in _instanceFactories)
{
yield return (T)instanceFactory(_scope);
yield return instanceFactory(_scope);
}
}

Expand All @@ -30,6 +30,6 @@ IEnumerator IEnumerable.GetEnumerator()

public int Count => _instanceFactories.Length;

public T this[int index] => (T)_instanceFactories[index](_scope);
public T this[int index] => _instanceFactories[index](_scope);
}
}
13 changes: 12 additions & 1 deletion src/Singularity/Collections/SinglyLinkedListNode.cs
Expand Up @@ -107,12 +107,23 @@ public void Reset()
}
}

internal static class SinglyLinkedListNodeExtensions
public static class SinglyLinkedListNodeExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static SinglyLinkedListNode<T> Add<T>(this SinglyLinkedListNode<T>? previous, in T value)
{
return new SinglyLinkedListNode<T>(previous, in value);
}

public static SinglyLinkedListNode<T>? ToSinglyLinkedList<T>(this IEnumerable<T> collection)
{
SinglyLinkedListNode<T>? previous = null;
foreach (var element in collection)
{
previous = new SinglyLinkedListNode<T>(previous, element);
}

return previous;
}
}
}
142 changes: 142 additions & 0 deletions src/Singularity/Graph/Resolvers/CollectionServiceBindingGenerator.cs
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Singularity.Collections;
using Singularity.Expressions;

namespace Singularity.Graph.Resolvers
{
/// <summary>
/// Creates bindings for resolving all services of a given type.
/// </summary>
public sealed class CollectionServiceBindingGenerator : IServiceBindingGenerator
{
private static readonly MethodInfo GenericResolveMethod = typeof(CollectionServiceBindingGenerator).GetRuntimeMethods().Single(x => x.Name == nameof(Resolve) && x.ContainsGenericParameters);

/// <inheritdoc />
public IEnumerable<ServiceBinding> Resolve(IResolverPipeline graph, Type type)
{
Type? elementType = null;
if (type.IsArray)
{
elementType = type.GetElementType();
}
else if (type.IsGenericType)
{
Type openGenericCollectionType = type.GetGenericTypeDefinition();

Type[] collectionTypes =
{
typeof(IEnumerable<>),
typeof(IReadOnlyCollection<>),
typeof(IReadOnlyList<>),

typeof(List<>),
typeof(ICollection<>),

typeof(IList<>),

typeof(HashSet<>),
typeof(ISet<>),
};
if (collectionTypes.Contains(openGenericCollectionType))
{
elementType = type.GenericTypeArguments[0];

}
}

if (elementType != null)
{
MethodInfo resolveMethod = GenericResolveMethod.MakeGenericMethod(elementType);

var bindings = (IEnumerable<ServiceBinding>)resolveMethod.Invoke(this, new object[] { graph, type });
foreach (var binding in bindings)
{
yield return binding;
}
}
}

private IEnumerable<ServiceBinding> Resolve<TElement>(IResolverPipeline graph, Type type)
{
Func<Scoped, TElement>[] instanceFactories = graph.TryResolveAll(typeof(TElement)).Select(x => (Func<Scoped, TElement>)(Delegate)x.Factory).ToArray();

yield return new ServiceBinding(new[]
{
typeof(Func<Scoped, TElement>[]),
}, BindingMetadata.GeneratedInstance, Expression.Constant(instanceFactories), typeof(Func<Scoped, TElement>[]), ConstructorResolvers.Default, Lifetimes.PerContainer);

Expression expression = ConstructorResolvers.Default.ResolveConstructorExpression(typeof(InstanceFactoryList<TElement>))!;

yield return new ServiceBinding(new[]
{
typeof(IEnumerable<TElement>),
typeof(IReadOnlyCollection<TElement>),
typeof(IReadOnlyList<TElement>),
}, BindingMetadata.GeneratedInstance, expression, expression.GetReturnType(), ConstructorResolvers.Default, Lifetimes.Transient);

//lists
Expression<Func<Scoped, List<TElement>>> listExpression = scope => CreateList(scope, instanceFactories);
yield return new ServiceBinding(new[]
{
typeof(List<TElement>),
typeof(ICollection<TElement>),
}, BindingMetadata.GeneratedInstance, listExpression, listExpression.GetReturnType(), ConstructorResolvers.Default, Lifetimes.Transient);

//sets
Expression<Func<Scoped, HashSet<TElement>>> setExpression = scope => CreateSet(scope, instanceFactories);
yield return new ServiceBinding(new[]
{
typeof(HashSet<TElement>),
typeof(ISet<TElement>),
}, BindingMetadata.GeneratedInstance, setExpression, setExpression.GetReturnType(), ConstructorResolvers.Default, Lifetimes.Transient);

//arrays
Expression<Func<Scoped, TElement[]>> arrayExpression = scope => CreateArray(scope, instanceFactories);
yield return new ServiceBinding(new[]
{
typeof(TElement[]),
typeof(IList<TElement>),
}, BindingMetadata.GeneratedInstance, arrayExpression, arrayExpression.GetReturnType(), ConstructorResolvers.Default, Lifetimes.Transient);
}

private static List<TElement> CreateList<TElement>(Scoped scope, Func<Scoped, TElement>[] instanceFactories)
{
var list = new List<TElement>(instanceFactories.Length);

foreach (Func<Scoped, TElement> instanceFactory in instanceFactories)
{
list.Add(instanceFactory.Invoke(scope));
}

return list;
}

private static HashSet<TElement> CreateSet<TElement>(Scoped scope, Func<Scoped, TElement>[] instanceFactories)
{
var list = new HashSet<TElement>();

foreach (Func<Scoped, TElement> instanceFactory in instanceFactories)
{
list.Add(instanceFactory.Invoke(scope));
}

return list;
}

private static TElement[] CreateArray<TElement>(Scoped scope, Func<Scoped, TElement>[] instanceFactories)
{
var list = new TElement[instanceFactories.Length];

for (int i = 0; i < instanceFactories.Length; i++)
{
list[i] = instanceFactories[i].Invoke(scope);
}

return list;
}
}
}
Expand Up @@ -13,7 +13,7 @@ public IEnumerable<ServiceBinding> Resolve(IResolverPipeline graph, Type type)
{
if (!type.IsInterface)
{
yield return new ServiceBinding(type, BindingMetadata.GeneratedInstance, graph.Settings.ConstructorResolver.ResolveConstructorExpression(type), type, graph.Settings.ConstructorResolver);
yield return new ServiceBinding(type, BindingMetadata.GeneratedInstance, graph.Settings.ConstructorResolver.ResolveConstructorExpression(type), type, graph.Settings.ConstructorResolver, Lifetimes.Transient);
}
}
}
Expand Down
Expand Up @@ -20,13 +20,13 @@ public IEnumerable<ServiceBinding> Resolve(IResolverPipeline graph, Type type)
if (type == typeof(Container))
{
Expression expression = Expression.Call(null, _getContainer, ExpressionGenerator.ScopeParameter);
yield return new ServiceBinding(type, BindingMetadata.GeneratedInstance, expression, type, graph.Settings.ConstructorResolver, Lifetimes.PerContainer, null, ServiceAutoDispose.Never);
yield return new ServiceBinding(type, BindingMetadata.GeneratedInstance, expression, type, ConstructorResolvers.Default, Lifetimes.PerContainer, null, ServiceAutoDispose.Never);
}

if (type == typeof(Scoped) || type == typeof(IServiceProvider))
{
Expression expression = Expression.Call(null, _getScope, ExpressionGenerator.ScopeParameter);
yield return new ServiceBinding(type, BindingMetadata.GeneratedInstance, expression, type, graph.Settings.ConstructorResolver, Lifetimes.PerContainer, null, ServiceAutoDispose.Never);
yield return new ServiceBinding(type, BindingMetadata.GeneratedInstance, expression, type, ConstructorResolvers.Default, Lifetimes.PerContainer, null, ServiceAutoDispose.Never);
}
}

Expand Down

This file was deleted.

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Singularity.Expressions;
Expand All @@ -11,7 +12,7 @@ namespace Singularity.Graph.Resolvers
/// </summary>
public sealed class ExpressionServiceBindingGenerator : IServiceBindingGenerator
{
private static readonly MethodInfo GenericCreateLambdaMethod = typeof(ExpressionServiceBindingGenerator).GetMethod(nameof(CreateLambda));
private static readonly MethodInfo GenericResolveMethod = typeof(ExpressionServiceBindingGenerator).GetRuntimeMethods().Single(x => x.Name == nameof(Resolve) && x.ContainsGenericParameters);

/// <inheritdoc />
public IEnumerable<ServiceBinding> Resolve(IResolverPipeline graph, Type type)
Expand All @@ -22,24 +23,29 @@ public IEnumerable<ServiceBinding> Resolve(IResolverPipeline graph, Type type)
if (funcType.GetGenericTypeDefinition() == typeof(Func<>) && funcType.GenericTypeArguments.Length == 1)
{
Type dependencyType = funcType.GenericTypeArguments[0];
MethodInfo method = GenericCreateLambdaMethod.MakeGenericMethod(dependencyType);
foreach (InstanceFactory instanceFactory in graph.TryResolveAll(dependencyType))
{
var newBinding = new ServiceBinding(type, BindingMetadata.GeneratedInstance, instanceFactory.Context.Expression, instanceFactory.Context.Expression.Type, graph.Settings.ConstructorResolver);
MethodInfo resolveMethod = GenericResolveMethod.MakeGenericMethod(dependencyType);

var expression = (Expression)method.Invoke(null, new object[] { instanceFactory.Context });
var factory = new InstanceFactory(type, new ExpressionContext(expression), scoped => expression);
newBinding.Factories.Add(factory);
yield return newBinding;
var bindings = (IEnumerable<ServiceBinding>)resolveMethod.Invoke(this, new object[] { graph, type });
foreach (var binding in bindings)
{
yield return binding;
}
}
}
}

public static LambdaExpression CreateLambda<T>(ReadOnlyExpressionContext context)
private IEnumerable<ServiceBinding> Resolve<TElement>(IResolverPipeline graph, Type type)
{
Expression expression = ExpressionCompiler.OptimizeExpression(context);
return Expression.Lambda<Func<T>>(expression);
foreach (InstanceFactory instanceFactory in graph.TryResolveAll(typeof(TElement)))
{
var newBinding = new ServiceBinding(type, BindingMetadata.GeneratedInstance, instanceFactory.Context.Expression, instanceFactory.Context.Expression.Type, ConstructorResolvers.Default, Lifetimes.Transient);

var expression = Expression.Lambda<Func<TElement>>(ExpressionCompiler.OptimizeExpression(instanceFactory.Context));
Func<Scoped, Expression<Func<TElement>>> del = scoped => expression; // we need to put this in a variable of this type or cast it else the static return type of the delegate will turn into a object..
var factory = new InstanceFactory(type, new ExpressionContext(expression), del);
newBinding.Factories.Add(factory);
yield return newBinding;
}
}
}
}
Expand Up @@ -21,7 +21,7 @@ public IEnumerable<ServiceBinding> Resolve(IResolverPipeline graph, Type type)
{
LambdaExpression baseExpression = Expression.Lambda(factory.Context.Expression);

yield return new ServiceBinding(type, BindingMetadata.GeneratedInstance, baseExpression, type, graph.Settings.ConstructorResolver)
yield return new ServiceBinding(type, BindingMetadata.GeneratedInstance, baseExpression, type, ConstructorResolvers.Default, Lifetimes.Transient)
{
BaseExpression = new ExpressionContext(baseExpression)
};
Expand Down
Expand Up @@ -25,7 +25,7 @@ public IEnumerable<ServiceBinding> Resolve(IResolverPipeline graph, Type type)
{
var context = (ExpressionContext)factory.Context;
context.Expression = Expression.New(constructor, factory.Context.Expression);
var newBinding = new ServiceBinding(type, BindingMetadata.GeneratedInstance, context.Expression, type, graph.Settings.ConstructorResolver);
var newBinding = new ServiceBinding(type, BindingMetadata.GeneratedInstance, context.Expression, type, ConstructorResolvers.Default, Lifetimes.Transient);
newBinding.Factories.Add(new InstanceFactory(type, context));
yield return newBinding;
}
Expand Down
19 changes: 16 additions & 3 deletions src/Singularity/Registration/ServiceBinding.cs
Expand Up @@ -107,11 +107,24 @@ public bool TryGetInstanceFactory(Type type, out InstanceFactory factory)
}

/// <summary>
/// Constructor that fills in some default values to make it more easier to use in <see cref="IServiceBindingGenerator"/>'s
/// Creates a new service binding using the provided data.
/// </summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidEnumValueException{T}"></exception>
public ServiceBinding(Type[] dependencyTypes, in BindingMetadata bindingMetadata, Expression? expression, Type concreteType, IConstructorResolver constructorResolver,
ILifetime lifetime, Action<object>? finalizer = null,
ServiceAutoDispose needsDispose = ServiceAutoDispose.Default) : this(dependencyTypes.ToSinglyLinkedList() ?? throw new ArgumentException("there should be atleast 1 dependency type", nameof(dependencyTypes)), bindingMetadata, expression, concreteType, constructorResolver, lifetime, finalizer, needsDispose)
{
}

/// <summary>
/// Creates a new service binding using the provided data.
/// </summary>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidEnumValueException{T}"></exception>
public ServiceBinding(Type dependencyType, in BindingMetadata bindingMetadata, Expression? expression, Type concreteType, IConstructorResolver constructorResolver,
ILifetime? lifetime = null, Action<object>? finalizer = null,
ServiceAutoDispose needsDispose = ServiceAutoDispose.Default) : this(new SinglyLinkedListNode<Type>(dependencyType), bindingMetadata, expression, concreteType, constructorResolver, lifetime ?? Lifetimes.Transient, finalizer, needsDispose)
ILifetime lifetime, Action<object>? finalizer = null,
ServiceAutoDispose needsDispose = ServiceAutoDispose.Default) : this(new SinglyLinkedListNode<Type>(dependencyType), bindingMetadata, expression, concreteType, constructorResolver, lifetime, finalizer, needsDispose)
{
}
}
Expand Down

0 comments on commit 20fdbab

Please sign in to comment.