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

Dependency Injection support for ValueTypes as Services #59625

Merged
merged 2 commits into from
Sep 28, 2021
Merged
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
@@ -162,7 +162,13 @@ protected override Expression VisitConstructor(ConstructorCallSite callSite, obj
parameterExpressions[i] = Convert(VisitCallSite(callSite.ParameterCallSites[i], context), parameters[i].ParameterType);
}
}
return Expression.New(callSite.ConstructorInfo, parameterExpressions);

Expression expression = Expression.New(callSite.ConstructorInfo, parameterExpressions);
if (callSite.ImplementationType.IsValueType)
{
expression = Expression.Convert(expression, typeof(object));
}
return expression;
}

private static Expression Convert(Expression expression, Type type, bool forceValueTypeConversion = false)
Original file line number Diff line number Diff line change
@@ -151,6 +151,11 @@ protected override object VisitConstructor(ConstructorCallSite constructorCallSi
}

argument.Generator.Emit(OpCodes.Newobj, constructorCallSite.ConstructorInfo);
if (constructorCallSite.ImplementationType.IsValueType)
{
argument.Generator.Emit(OpCodes.Box, constructorCallSite.ImplementationType);
}

return null;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.DependencyInjection.Specification.Fakes;

namespace Microsoft.Extensions.DependencyInjection.Fakes
{
public struct StructServiceWithNoDependencies: IFakeService
{
public StructServiceWithNoDependencies()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -163,7 +163,7 @@ public void CreatingServiceProviderWithUnresolvableTypesThrows(Type serviceType,
serviceCollection.AddTransient(serviceType, implementationType);

// Act and Assert
var exception = Assert.Throws<ArgumentException>(() => serviceCollection.BuildServiceProvider());
var exception = Assert.Throws<ArgumentException>(() => CreateServiceProvider(serviceCollection));
Assert.Equal(
$"Cannot instantiate implementation type '{implementationType}' for service type '{serviceType}'.",
exception.Message);
@@ -178,7 +178,7 @@ public void CreatingServiceProviderWithUnresolvableOpenGenericTypesThrows(Type s
serviceCollection.AddTransient(serviceType, implementationType);

// Act and Assert
var exception = Assert.Throws<ArgumentException>(() => serviceCollection.BuildServiceProvider());
var exception = Assert.Throws<ArgumentException>(() => CreateServiceProvider(serviceCollection));
Assert.StartsWith(errorMessage, exception.Message);
}

@@ -363,7 +363,7 @@ public void GetService_DisposeOnSameThread_Throws()
{
var services = new ServiceCollection();
services.AddSingleton<DisposeServiceProviderInCtor>();
IServiceProvider sp = services.BuildServiceProvider();
IServiceProvider sp = CreateServiceProvider(services);
Assert.Throws<ObjectDisposedException>(() =>
{
// ctor disposes ServiceProvider
@@ -380,7 +380,7 @@ public void GetAsyncService_DisposeAsyncOnSameThread_ThrowsAndDoesNotHangAndDisp
services.AddSingleton<DisposeServiceProviderInCtorAsyncDisposable>(sp =>
new DisposeServiceProviderInCtorAsyncDisposable(asyncDisposableResource, sp));

var sp = services.BuildServiceProvider();
var sp = CreateServiceProvider(services);
bool doesNotHang = Task.Run(() =>
{
SingleThreadedSynchronizationContext.Run(() =>
@@ -407,7 +407,7 @@ public void GetService_DisposeOnSameThread_ThrowsAndDoesNotHangAndDisposeGetsCal
services.AddSingleton<DisposeServiceProviderInCtorDisposable>(sp =>
new DisposeServiceProviderInCtorDisposable(disposableResource, sp));

var sp = services.BuildServiceProvider();
var sp = CreateServiceProvider(services);
bool doesNotHang = Task.Run(() =>
{
SingleThreadedSynchronizationContext.Run(() =>
@@ -447,7 +447,8 @@ public async Task AddDisposablesAndAsyncDisposables_DisposeAsync_AllDisposed(boo
//forces Dispose ValueTask to be asynchronous and not be immediately completed
services.AddSingleton<DelayedAsyncDisposableService>();
}
ServiceProvider sp = services.BuildServiceProvider();
ServiceProvider sp = (ServiceProvider)CreateServiceProvider(services);

var disposable = sp.GetRequiredService<Disposable>();
var asyncDisposable = sp.GetRequiredService<AsyncDisposable>();
DelayedAsyncDisposableService delayedAsyncDisposableService = null;
@@ -541,7 +542,7 @@ public async Task GetRequiredService_ResolvingSameSingletonInTwoThreads_SameServ
services.AddSingleton<OuterSingleton>();
services.AddSingleton(sp => new InnerSingleton(mreForThread1, mreForThread2));

sp = services.BuildServiceProvider();
sp = CreateServiceProvider(services);

var t1 = Task.Run(() =>
{
@@ -637,7 +638,7 @@ public async Task GetRequiredService_UsesSingletonAndLazyLocks_NoDeadlock()
});
services.AddSingleton<Thing2>();

sp = services.BuildServiceProvider();
sp = CreateServiceProvider(services);

var t1 = Task.Run(() =>
{
@@ -717,7 +718,7 @@ public async Task GetRequiredService_BiggerObjectGraphWithOpenGenerics_NoDeadloc
});
services.AddSingleton<Thing5>();

sp = services.BuildServiceProvider();
sp = CreateServiceProvider(services);

// Act
var t1 = Task.Run(() =>
@@ -794,7 +795,6 @@ public Thing1(Thing0 thing0)

private class Thing0 { }

[ActiveIssue("https://github.com/dotnet/runtime/issues/42160")] // We don't support value task services currently
[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
@@ -808,7 +808,62 @@ public void WorksWithStructServices(ServiceLifetime lifetime)

var provider = CreateServiceProvider(serviceCollection);
var service = provider.GetService<IFakeMultipleService>();

Assert.NotNull(service);
allantargino marked this conversation as resolved.
Show resolved Hide resolved
Assert.IsType<StructFakeMultipleService>(service);
}

[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)]
public void WorksWithFactoryStructServices(ServiceLifetime lifetime)
{
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.Add(new ServiceDescriptor(typeof(IFakeService), _ => new StructServiceWithNoDependencies(), lifetime));

var provider = CreateServiceProvider(serviceCollection);
var service = provider.GetService<IFakeService>();

Assert.NotNull(service);
Assert.IsType<StructServiceWithNoDependencies>(service);
}

[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)]
public void WorksWithFactoryStructServicesAsDependencies(ServiceLifetime lifetime)
{
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.Add(new ServiceDescriptor(typeof(IFakeService), _ => new StructServiceWithNoDependencies(), lifetime));
serviceCollection.Add(new ServiceDescriptor(typeof(StructService), typeof(StructService), lifetime));
serviceCollection.Add(new ServiceDescriptor(typeof(IFakeMultipleService), typeof(StructFakeMultipleService), lifetime));

var provider = CreateServiceProvider(serviceCollection);
var service = provider.GetService<IFakeMultipleService>();

Assert.NotNull(service);
Assert.IsType<StructFakeMultipleService>(service);
}

[Theory]
[InlineData(ServiceLifetime.Transient)]
[InlineData(ServiceLifetime.Scoped)]
[InlineData(ServiceLifetime.Singleton)]
public void WorksWithIEnumerableStructServices(ServiceLifetime lifetime)
{
IServiceCollection serviceCollection = new ServiceCollection();
for (int i = 0; i < 10; i++)
{
serviceCollection.Add(new ServiceDescriptor(typeof(IFakeService), typeof(StructServiceWithNoDependencies), lifetime));
}

var provider = CreateServiceProvider(serviceCollection);
var services = provider.GetService<IEnumerable<IFakeService>>();

Assert.Equal(10, services.Count());
Assert.All(services, service => Assert.IsType<StructServiceWithNoDependencies>(service));
}

[Fact]
@@ -821,8 +876,11 @@ public void WorksWithWideScopedTrees()
serviceCollection.AddScoped<IFakeMultipleService, FakeMultipleServiceWithIEnumerableDependency>();
serviceCollection.AddScoped<IFakeService, FakeService>();
}
var serviceProvider = CreateServiceProvider(serviceCollection);

var service = CreateServiceProvider(serviceCollection).GetService<IEnumerable<IFakeOuterService>>();
var services = serviceProvider.GetService<IEnumerable<IFakeOuterService>>();

Assert.Equal(20, services.Count());
}

[Fact]
@@ -834,7 +892,7 @@ public void GenericIEnumerableItemCachedInTheRightSlot()
// Doesn't matter what this services is, we just want something in the collection after generic registration
services.AddSingleton<FakeService>();

var serviceProvider = services.BuildServiceProvider();
var serviceProvider = CreateServiceProvider(services);

var serviceRef1 = serviceProvider.GetRequiredService<IFakeOpenGenericService<PocoClass>>();
var servicesRef1 = serviceProvider.GetServices<IFakeOpenGenericService<PocoClass>>().Single();
@@ -1156,9 +1214,9 @@ public async Task GetRequiredService_ResolveUniqueServicesConcurrently_StressTes
[Fact]
public void ScopedServiceResolvedFromSingletonAfterCompilation()
{
ServiceProvider sp = new ServiceCollection()
.AddScoped<A>()
.BuildServiceProvider();
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<A>();
var sp = CreateServiceProvider(serviceCollection);

var singleton = sp.GetRequiredService<A>();
for (int i = 0; i < 10; i++)
@@ -1168,18 +1226,13 @@ public void ScopedServiceResolvedFromSingletonAfterCompilation()
}
}

[Theory]
[InlineData(ServiceProviderMode.Default)]
[InlineData(ServiceProviderMode.Dynamic)]
[InlineData(ServiceProviderMode.Runtime)]
[InlineData(ServiceProviderMode.Expressions)]
[InlineData(ServiceProviderMode.ILEmit)]
private void ScopedServiceResolvedFromSingletonAfterCompilation2(ServiceProviderMode mode)
{
ServiceProvider sp = new ServiceCollection()
.AddScoped<A>()
.AddSingleton<IFakeOpenGenericService<A>, FakeOpenGenericService<A>>()
.BuildServiceProvider(mode);
[Fact]
public void ScopedServiceResolvedFromSingletonAfterCompilation2()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<A>()
.AddSingleton<IFakeOpenGenericService<A>, FakeOpenGenericService<A>>();
var sp = CreateServiceProvider(serviceCollection);

var scope = sp.CreateScope();
for (int i = 0; i < 50; i++)
@@ -1191,20 +1244,15 @@ private void ScopedServiceResolvedFromSingletonAfterCompilation2(ServiceProvider
Assert.Same(sp.GetRequiredService<IFakeOpenGenericService<A>>().Value, sp.GetRequiredService<A>());
}

[Theory]
[InlineData(ServiceProviderMode.Default)]
[InlineData(ServiceProviderMode.Dynamic)]
[InlineData(ServiceProviderMode.Runtime)]
[InlineData(ServiceProviderMode.Expressions)]
[InlineData(ServiceProviderMode.ILEmit)]
private void ScopedServiceResolvedFromSingletonAfterCompilation3(ServiceProviderMode mode)
[Fact]
public void ScopedServiceResolvedFromSingletonAfterCompilation3()
{
// Singleton IFakeX<A> -> Scoped A -> Scoped Aa
ServiceProvider sp = new ServiceCollection()
.AddScoped<Aa>()
.AddScoped<A>()
.AddSingleton<IFakeOpenGenericService<Aa>, FakeOpenGenericService<Aa>>()
.BuildServiceProvider(mode);
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<Aa>()
.AddScoped<A>()
.AddSingleton<IFakeOpenGenericService<Aa>, FakeOpenGenericService<Aa>>();
var sp = CreateServiceProvider(serviceCollection);

var scope = sp.CreateScope();
for (int i = 0; i < 50; i++)