JasperFx / lamar

Fast Inversion of Control Tool and Successor to StructureMap
https://jasperfx.github.io/lamar
MIT License
571 stars 119 forks source link

Lamar throws misleading System.MissingMethodException:'constructor on type 'X' not found' when registering a singleton instead of the actual internally known reason. #275

Closed Amiron49 closed 3 years ago

Amiron49 commented 3 years ago

I didn't configure a dependency Lamar needed for constructing a class but Lamar swallowed the Error and tries to resolve a non existing ctor on the class and throws a System.MissingMethodException:"Constructor on type 'X' not found.", instead of serving the issue it knows about internally.

It seems like Lamar has a different Exception throwing behavior when registering something as a singleton vs the default (transient I guess). Here is a quick reproduction:

using System;
using Lamar;

namespace QuickLamarTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var helpful = new Container(services =>
            {
                services.For<IFoo>().Use<Foo>(); //expected Exception
            });
            GetInstanceAndPrintError("helpful", helpful);

            var helpful2 = new Container(services =>
            {
                services.For<IFoo>().Use<Foo>().Transient();
            });
            GetInstanceAndPrintError("helpful2", helpful2); //expected Exception

            var notHelpful = new Container(services =>
            {
                services.For<IFoo>().Use<Foo>().Singleton();
            });
            GetInstanceAndPrintError("not helpful", notHelpful); //wrong Exception

            Console.ReadKey();
        }

        private static void GetInstanceAndPrintError(string context, IServiceContext container)
        {
            try
            {
                container.GetInstance<IFoo>();
            }
            catch (Exception e)
            {
                Console.WriteLine($"{context}: {e.Message}");
            }
        }
    }

    public class Foo : IFoo
    {
        public Foo(IRandomDependency dependency)
        {
        }
    }

    public interface IFoo
    {
    }

    public interface IRandomDependency
    {
    }
}

Console Output:

helpful: Cannot build registered instance foo of 'QuickLamarTest.IFoo':
Cannot fill the dependencies of any of the public constructors
Available constructors:new Foo(IRandomDependency dependency)
* IRandomDependency is not registered within this container and cannot be auto discovered by any missing family policy

helpful2: Cannot build registered instance foo of 'QuickLamarTest.IFoo':
Cannot fill the dependencies of any of the public constructors
Available constructors:new Foo(IRandomDependency dependency)
* IRandomDependency is not registered within this container and cannot be auto discovered by any missing family policy

not helpful: Constructor on type `QuickLamarTest.Foo` not found.

In my case I found the real reason by breaking on the Exception, going up the stacktrace to the closest Lamar.IoC.Instances.ConstructorInstance.quickResolve(Scope scope). On the ConstructorInstance there is a Property ErrorMessages, explaining that it had issue with one of the ctor arguments. And looking at what exactly Lamar is doing is a call to Activator.CreateInstance but with 0 CtorArguments (this.Arguments is empty). But the only existing ctor is one with 1 argument, so the activator rightfully throws as the Ctor Lamar is attempting to activate does not exist.

I'm 80% sure that is also the real reason for this other issue: #210

Stack Trace:

   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at System.Activator.CreateInstance(Type type, Object[] args)
   at Lamar.IoC.Instances.ConstructorInstance.quickResolve(Scope scope)
   at Lamar.IoC.Instances.ConstructorInstance.<>c__DisplayClass15_0.<ToResolver>b__0(Scope s)
   at Lamar.ServiceGraph.<>c__DisplayClass49_0.<FindResolver>b__0(Scope s)
   at Lamar.IoC.Scope.GetInstance(Type serviceType)
   at Lamar.IoC.Scope.GetInstance[T]()
   at QuickLamarTest.Program.GetInstanceAndPrintError(String context, IServiceContext container) in V:\Projects\QuickLamarTest\QuickLamarTest\Program.cs:line 38
jeremydmiller commented 3 years ago

Got it, thank you for the reproduction steps, and the fix for this will be in Lamar 5.0.3 shortly.

Because Lamar only creates a singleton once, it uses pure Reflection to build a singleton rather than wasting any time building and compiling a Func<Scope, object> to do that.