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

DI behavior change between .NET 7.0 and .NET 8.0 #88968

Closed
kevinchalet opened this issue Jul 16, 2023 · 5 comments
Closed

DI behavior change between .NET 7.0 and .NET 8.0 #88968

kevinchalet opened this issue Jul 16, 2023 · 5 comments

Comments

@kevinchalet
Copy link
Contributor

Description

The snippet attached to this ticket works fine on .NET 7.0 (and earlier) but crashes on .NET 8.0 preview6 (changing <TargetFramework>net8.0</TargetFramework> to <TargetFramework>net7.0</TargetFramework> is enough to stop the exception).

Reproduction Steps

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0-preview.6.23329.7" />
    <PackageReference Include="Moq" Version="4.18.4" />
  </ItemGroup>

</Project>
using Microsoft.Extensions.DependencyInjection;
using Moq;

var mock = Mock.Of<IInterface>();

var services = new ServiceCollection();
services.AddSingleton(typeof(IInterface), mock.GetType());

await using var provider = services.BuildServiceProvider();
var instance = provider.GetRequiredService<IInterface>();

Console.WriteLine($"Instance has the correct type: {instance.GetType() == mock.GetType()}");

public interface IInterface { }

Expected behavior

No exception, Instance has the correct type: True visible in the console output.

Actual behavior

System.InvalidOperationException : 'Unable to resolve service for type 'Castle.DynamicProxy.IInterceptor[]' while attempting to activate 'Castle.Proxies.IInterfaceProxy'.'

Regression?

Yes, working fine on .NET 7.0 and earlier.

Known Workarounds

No response

Configuration

No response

Other information

Changing the Microsoft.Extensions.DependencyInjection package version doesn't seem to have any influence.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jul 16, 2023
@ghost
Copy link

ghost commented Jul 16, 2023

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

The snippet attached to this ticket works fine on .NET 7.0 (and earlier) but crashes on .NET 8.0 preview6 (changing <TargetFramework>net8.0</TargetFramework> to <TargetFramework>net7.0</TargetFramework> is enough to stop the exception).

Reproduction Steps

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0-preview.6.23329.7" />
    <PackageReference Include="Moq" Version="4.18.4" />
  </ItemGroup>

</Project>
using Microsoft.Extensions.DependencyInjection;
using Moq;

var mock = Mock.Of<IInterface>();

var services = new ServiceCollection();
services.AddSingleton(typeof(IInterface), mock.GetType());

await using var provider = services.BuildServiceProvider();
var instance = provider.GetRequiredService<IInterface>();

Console.WriteLine($"Instance has the correct type: {instance.GetType() == mock.GetType()}");

public interface IInterface { }

Expected behavior

No exception, Instance has the correct type: True visible in the console output.

Actual behavior

System.InvalidOperationException : 'Unable to resolve service for type 'Castle.DynamicProxy.IInterceptor[]' while attempting to activate 'Castle.Proxies.IInterfaceProxy'.'

Regression?

Yes, working fine on .NET 7.0 and earlier.

Known Workarounds

No response

Configuration

No response

Other information

Changing the Microsoft.Extensions.DependencyInjection package version doesn't seem to have any influence.

Author: kevinchalet
Assignees: -
Labels:

untriaged, area-Extensions-DependencyInjection

Milestone: -

@teo-tsirpanis
Copy link
Contributor

Did not reproduce with Preview 4, just installed Preview 6 and it does. Also reproduces with version 7.0.0 of MS.E.DI. Here's the full stack trace:

Unhandled exception. System.InvalidOperationException: Unable to resolve service for type 'Castle.DynamicProxy.IInterceptor[]' while attempting to activate 'Castle.Proxies.IInterfaceProxy'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Program.<Main>$(String[] args) in C:\Users\teo\code\DiRepro\Program.cs:line 10
   at Program.<Main>$(String[] args) in C:\Users\teo\code\DiRepro\Program.cs:line 12
   at Program.<Main>(String[] args)

Seems like a reflection change that broke DI. @dotnet/area-system-reflection thoguhts?

@steveharter steveharter self-assigned this Jul 17, 2023
@steveharter steveharter removed the untriaged New issue has not been triaged by the area owner label Jul 17, 2023
@steveharter steveharter added this to the 8.0.0 milestone Jul 17, 2023
@steveharter
Copy link
Member

steveharter commented Jul 17, 2023

I believe this was caused by #84550 which fixes an issue with emit that used to add a default value of null for emit'd arguments even though it was not emit'd that way. Now DI can't find the constructor of the Moq-generated object.

The Moq-generated object contains only 1 ctor with the following args:

{Name = "IInterceptor[]" FullName = "Castle.DynamicProxy.IInterceptor[]"}
{Name = "Object" FullName = "System.Object"}

which are not emit'd with default values.

I haven't reseached Moq to see if there is some sort of feature to generate a default ctor for implemented interfaces.

For the super simple sample provided in the issue, adding:

var mock = new Mock<IInterfaceImpl>().Object;
public class IInterfaceImpl : IInterface { }

shows the issue - the class will now contain a default ctor.

I also added the "needs breaking change doc" flag to that issue.

@kevinchalet
Copy link
Contributor Author

@steveharter thanks for taking the time to investigate, much appreciated! 😃

The suggested fix is pretty much what I had in mind but I preferred reporting this issue first, given that this code has always worked fine on .NET Framework 4.6.1/4.7.2/4.8 and every .NET Core version from 1.0 to 7.0. If the outcome is "it worked, but only by accident", I'm fine with that 👍🏻

Thanks again!

@steveharter
Copy link
Member

I'm going to close this as by design, and due to the breaking change above.

If DI needs to work around this issue, please re-open or comment here.

@teo-tsirpanis teo-tsirpanis closed this as not planned Won't fix, can't repro, duplicate, stale Jul 19, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Aug 18, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants