Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
public class ServiceCollection : IServiceCollection
{
private static readonly object _syncLock = new();
private readonly ArrayList _descriptors = new();
private readonly object _syncLock = new();
private readonly ArrayList _descriptors = [];

/// <inheritdoc/>
public bool IsReadOnly => false;
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,10 @@ public class ServiceDescriptor
/// <param name="lifetime">The <see cref="ServiceLifetime"/> of the service.</param>
/// <exception cref="ArgumentNullException"><paramref name="serviceType"/> or <paramref name="implementationType"/> can't be null</exception>
/// <exception cref="ArgumentException">Implementation type cannot be an abstract or interface class.</exception>
public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime)
: this(serviceType, lifetime)
public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime): this(serviceType, lifetime)
{
if (serviceType == null)
{
throw new ArgumentNullException();
}

if (implementationType == null)
{
throw new ArgumentNullException();
}
ArgumentNullException.ThrowIfNull(serviceType);
ArgumentNullException.ThrowIfNull(implementationType);

if (implementationType.IsAbstract || implementationType.IsInterface)
{
Expand All @@ -55,21 +47,13 @@ public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifet
/// <param name="serviceType">The <see cref="Type"/> of the service.</param>
/// <param name="instance">The instance implementing the service.</param>
/// <exception cref="ArgumentNullException"><paramref name="serviceType"/> or <paramref name="instance"/> can't be <see langword="null"/></exception>
public ServiceDescriptor(Type serviceType, object instance)
: this(serviceType, ServiceLifetime.Singleton)
public ServiceDescriptor(Type serviceType, object instance): this(serviceType, ServiceLifetime.Singleton)
{
if (serviceType == null)
{
throw new ArgumentNullException();
}

if (instance == null)
{
throw new ArgumentNullException();
}
ArgumentNullException.ThrowIfNull(serviceType);
ArgumentNullException.ThrowIfNull(instance);

ImplementationInstance = instance;
ImplementationType = GetImplementationType();
ImplementationType = instance.GetType();
}

/// <summary>
Expand All @@ -81,15 +65,12 @@ public ServiceDescriptor(Type serviceType, object instance)
/// <exception cref="ArgumentNullException"><paramref name="serviceType"/> can't be null</exception>
/// <exception cref="ArgumentNullException"><paramref name="implementationFactory"/> can't be null</exception>
/// <exception cref="ArgumentException">Implementation type cannot be an abstract or interface class.</exception>
public ServiceDescriptor(Type serviceType, ImplementationFactoryDelegate implementationFactory, ServiceLifetime lifetime)
: this(serviceType, lifetime)
public ServiceDescriptor(Type serviceType, ImplementationFactoryDelegate implementationFactory, ServiceLifetime lifetime): this(serviceType, lifetime)
{
if (serviceType == null)
{
throw new ArgumentNullException();
}
ArgumentNullException.ThrowIfNull(serviceType);
ArgumentNullException.ThrowIfNull(implementationFactory);

ImplementationFactory = implementationFactory ?? throw new ArgumentNullException();
ImplementationFactory = implementationFactory;
}

private ServiceDescriptor(Type serviceType, ServiceLifetime lifetime)
Expand Down Expand Up @@ -151,6 +132,18 @@ public Type GetImplementationType()
{
return ImplementationInstance.GetType();
}
// TODO: This case is not currently handled which means we cannot currently support factories in the TryAdd methods.
// A possible solution would be to require the implementation type to be supplied with the factory.
/*
else if (ImplementationFactory != null)
{
Type[]? typeArguments = ImplementationFactory.GetType().GenericTypeArguments;

Debug.Assert(typeArguments.Length == 2);

return typeArguments[1];
}
*/

Debug.Assert(false, "ImplementationType and ImplementationInstance must be non null");

Expand All @@ -168,7 +161,105 @@ public Type GetImplementationType()
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime)
{
ArgumentNullException.ThrowIfNull(serviceType);
ArgumentNullException.ThrowIfNull(implementationType);

return new ServiceDescriptor(serviceType, implementationType, lifetime);
}

/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="serviceType"/>, <paramref name="implementationFactory"/>,
/// and <paramref name="lifetime"/>.
/// </summary>
/// <param name="serviceType">The type of the service.</param>
/// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
/// <param name="lifetime">The lifetime of the service.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Describe(Type serviceType, ImplementationFactoryDelegate implementationFactory, ServiceLifetime lifetime)
{
ArgumentNullException.ThrowIfNull(serviceType);
ArgumentNullException.ThrowIfNull(implementationFactory);

return new ServiceDescriptor(serviceType, implementationFactory, lifetime);
}

/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="service"/> and <paramref name="implementationType"/>
/// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
/// </summary>
/// <param name="service">The type of the service.</param>
/// <param name="implementationType">The type of the implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Scoped(Type service, Type implementationType)
{
return Describe(service, implementationType, ServiceLifetime.Scoped);
}

/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="service"/> and <paramref name="implementationFactory"/>
/// and the <see cref="ServiceLifetime.Scoped"/> lifetime.
/// </summary>
/// <param name="service">The type of the service.</param>
/// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Scoped(Type service, ImplementationFactoryDelegate implementationFactory)
{
return Describe(service, implementationFactory, ServiceLifetime.Scoped);
}

/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="service"/> and <paramref name="implementationType"/>
/// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
/// </summary>
/// <param name="service">The type of the service.</param>
/// <param name="implementationType">The type of the implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Singleton(Type service, Type implementationType)
{
return Describe(service, implementationType, ServiceLifetime.Singleton);
}

/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="service"/> and <paramref name="implementationFactory"/>
/// and the <see cref="ServiceLifetime.Singleton"/> lifetime.
/// </summary>
/// <param name="service">The type of the service.</param>
/// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Singleton(Type service, ImplementationFactoryDelegate implementationFactory)
{
return Describe(service, implementationFactory, ServiceLifetime.Singleton);
}

/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="service"/> and <paramref name="implementationType"/>
/// and the <see cref="ServiceLifetime.Transient"/> lifetime.
/// </summary>
/// <param name="service">The type of the service.</param>
/// <param name="implementationType">The type of the implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Transient(Type service, Type implementationType)
{
return Describe(service, implementationType, ServiceLifetime.Transient);
}

/// <summary>
/// Creates an instance of <see cref="ServiceDescriptor"/> with the specified
/// <paramref name="service"/> and <paramref name="implementationFactory"/>
/// and the <see cref="ServiceLifetime.Transient"/> lifetime.
/// </summary>
/// <param name="service">The type of the service.</param>
/// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
/// <returns>A new instance of <see cref="ServiceDescriptor"/>.</returns>
public static ServiceDescriptor Transient(Type service, ImplementationFactoryDelegate implementationFactory)
{
return Describe(service, implementationFactory, ServiceLifetime.Transient);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<TargetFrameworkVersion>v1.0</TargetFrameworkVersion>
<NF_IsCoreLibrary>True</NF_IsCoreLibrary>
<DocumentationFile>bin\$(Configuration)\nanoFramework.DependencyInjection.xml</DocumentationFile>
<LangVersion>default</LangVersion>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<RestoreLockedMode Condition="'$(TF_BUILD)' == 'True' or '$(ContinuousIntegrationBuild)' == 'True'">true</RestoreLockedMode>
</PropertyGroup>
Expand All @@ -33,6 +34,7 @@
<ItemGroup>
<Compile Include="Microsoft\Extensions\DependencyInjection\ActivatorUtilities.cs" />
<Compile Include="Microsoft\Extensions\DependencyInjection\IServiceCollection.cs" />
<Compile Include="Microsoft\Extensions\DependencyInjection\ServiceCollectionDescriptorExtensions.cs" />
<Compile Include="Microsoft\Extensions\DependencyInjection\ServiceScope.cs" />
<Compile Include="Microsoft\Extensions\DependencyInjection\IServiceScope.cs" />
<Compile Include="Microsoft\Extensions\DependencyInjection\ServiceProviderEngine.cs" />
Expand All @@ -56,14 +58,14 @@
<None Include="..\.editorconfig" Link=".editorconfig" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="packages.lock.json" />
</ItemGroup>
<ItemGroup>
<Reference Include="mscorlib, Version=1.16.1.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
<HintPath>..\packages\nanoFramework.CoreLibrary.1.16.1\lib\mscorlib.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include="packages.lock.json" />
</ItemGroup>
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.CSharp.targets" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.CSharp.targets')" />
<ProjectExtensions>
<ProjectCapabilities>
Expand Down
156 changes: 156 additions & 0 deletions tests/ServiceCollectionDescriptorExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using nanoFramework.DependencyInjection.UnitTests.Fakes;
using nanoFramework.TestFramework;

// ReSharper disable InvokeAsExtensionMethod
namespace nanoFramework.DependencyInjection.UnitTests
{
[TestClass]
public class ServiceCollectionDescriptorExtensionsTests
{
private static ServiceDescriptor CreateSingletonServiceDescriptor() => ServiceDescriptor.Singleton(typeof(IService1), typeof(Service1));
private static ServiceDescriptor CreateTransientServiceDescriptor() => ServiceDescriptor.Transient(typeof(IService1), typeof(Service1));

[TestMethod]
public void Add_adds_descriptor()
{
var descriptor = CreateSingletonServiceDescriptor();

var collection = new ServiceCollection();

ServiceCollectionDescriptorExtensions.Add(collection, descriptor);

Assert.AreEqual(1, collection.Count);
Assert.IsTrue(collection.Contains(descriptor));
}

[TestMethod]
public void Add_adds_descriptors()
{
var singletonDescriptor = CreateSingletonServiceDescriptor();
var transientDescriptor = CreateTransientServiceDescriptor();
var descriptors = new[] { singletonDescriptor, transientDescriptor };

var collection = new ServiceCollection();

ServiceCollectionDescriptorExtensions.Add(collection, descriptors);

Assert.AreEqual(2, collection.Count);
Assert.IsTrue(collection.Contains(singletonDescriptor));
Assert.IsTrue(collection.Contains(transientDescriptor));
}

[TestMethod]
public void Add_throws_when_collection_is_null()
{
var test = new object[]{};
Assert.ThrowsException(typeof(ArgumentNullException), () => ServiceCollectionDescriptorExtensions.Add(null!, CreateSingletonServiceDescriptor()));
Assert.ThrowsException(typeof(ArgumentNullException), () => ServiceCollectionDescriptorExtensions.Add(null!, new ServiceDescriptor[]{}));
}

[TestMethod]
public void Add_throws_when_descriptor_is_null()
{
Assert.ThrowsException(typeof(ArgumentNullException), () => ServiceCollectionDescriptorExtensions.Add(new ServiceCollection(), ((ServiceDescriptor)null)!));
}

[TestMethod]
public void Add_throws_when_descriptors_contains_invalid_type()
{
var descriptors = new[]
{
CreateSingletonServiceDescriptor(),
CreateTransientServiceDescriptor(),
new object()
};

Assert.ThrowsException(typeof(ArgumentException), () => ServiceCollectionDescriptorExtensions.Add(new ServiceCollection(), descriptors));
}

[TestMethod]
public void Add_throws_when_descriptors_is_null()
{
Assert.ThrowsException(typeof(ArgumentNullException), () => ServiceCollectionDescriptorExtensions.Add(new ServiceCollection(), ((ServiceDescriptor[])null)!));
}

[TestMethod]
public void RemoveAll_removes_descriptors()
{
var serviceA = ServiceDescriptor.Scoped(typeof(IService1), typeof(Service1));
var serviceB = ServiceDescriptor.Singleton(typeof(IService1), typeof(Service1));
var serviceC = ServiceDescriptor.Transient(typeof(IService1), typeof(Service1));
var serviceD = ServiceDescriptor.Singleton(typeof(IService2), typeof(Service2));

var collection = new ServiceCollection();

collection.Add(serviceA);
collection.Add(serviceB);
collection.Add(serviceC);
collection.Add(serviceD);

Assert.AreEqual(4, collection.Count);
Assert.IsTrue(collection.Contains(serviceA));
Assert.IsTrue(collection.Contains(serviceB));
Assert.IsTrue(collection.Contains(serviceC));
Assert.IsTrue(collection.Contains(serviceD));

ServiceCollectionDescriptorExtensions.RemoveAll(collection, typeof(IService1));

Assert.AreEqual(1, collection.Count);
Assert.IsFalse(collection.Contains(serviceA));
Assert.IsFalse(collection.Contains(serviceB));
Assert.IsFalse(collection.Contains(serviceC));
Assert.IsTrue(collection.Contains(serviceD));
}

[TestMethod]
public void RemoveAll_throws_when_collection_is_null()
{
Assert.ThrowsException(typeof(ArgumentNullException), () => ServiceCollectionDescriptorExtensions.RemoveAll(null!, typeof(IService1)));
}

[TestMethod]
public void RemoveAll_throws_when_serviceType_is_null()
{
Assert.ThrowsException(typeof(ArgumentNullException), () => ServiceCollectionDescriptorExtensions.RemoveAll(new ServiceCollection(), null!));
}

[TestMethod]
public void Replace_replaces_descriptor()
{
var singletonDescriptor = CreateSingletonServiceDescriptor();
var transientDescriptor = CreateTransientServiceDescriptor();
var otherDescriptor = ServiceDescriptor.Singleton(typeof(IService2), typeof(Service2));

var collection = new ServiceCollection();

collection.Add(singletonDescriptor);
collection.Add(otherDescriptor);

Assert.AreEqual(2, collection.Count);
Assert.IsTrue(collection.Contains(singletonDescriptor));
Assert.IsFalse(collection.Contains(transientDescriptor));
Assert.IsTrue(collection.Contains(otherDescriptor));

ServiceCollectionDescriptorExtensions.Replace(collection, transientDescriptor);

Assert.AreEqual(2, collection.Count);
Assert.IsFalse(collection.Contains(singletonDescriptor));
Assert.IsTrue(collection.Contains(transientDescriptor));
Assert.IsTrue(collection.Contains(otherDescriptor));
}

[TestMethod]
public void Replace_throws_when_collection_is_null()
{
Assert.ThrowsException(typeof(ArgumentNullException), () => ServiceCollectionDescriptorExtensions.Replace(null!, CreateSingletonServiceDescriptor()));
}

[TestMethod]
public void Replace_throws_when_descriptor_is_null()
{
Assert.ThrowsException(typeof(ArgumentNullException), () => ServiceCollectionDescriptorExtensions.Replace(new ServiceCollection(), null!));
}
}
}
Loading