Generic Decorator Chains - StackOverflowException

Oct 16, 2008 at 10:26 PM
Chris,

Are generic decorator chains working in the latest drops of Unity 1.2?

Anytime I try to chain 3 or more at a time using the latest changeset ( 12774 ) I get a stack overflow error on the line below:

"An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll"


private
void GuardSameNumberOfGenericArguments(Type sourceType)
{
    
if(sourceType.GetGenericArguments().Length != DestinationType.GetGenericArguments().Length)  // Error occurs here...

 

Regards,

Dave

Oct 16, 2008 at 10:45 PM
Please send me a repro case ASAP. We're buttoning up the release, and if there's an actual problem here we need to fix it by the end of the week.

It should work.

Oct 17, 2008 at 3:49 AM
Chris,

It always could be user error :)

Here is an example that I would expect to work, but I get the StackOverflowException.


using System;

using Microsoft.Practices.Unity;

 

namespace CommandDecoratorExample

{

    class Program

    {

        static void Main(string[] args)

        {

            IUnityContainer container = new UnityContainer();

            container.RegisterType(typeof (IRepository<>), typeof (RepositoryDecorator2<>));

            container.RegisterType(typeof (IRepository<>), typeof (RepositoryDecorator1<>),"Decorator1");

            container.RegisterType(typeof (IRepository<>), typeof (Repository<>),"Repository");

 

            container.Configure<InjectedMembers>().ConfigureInjectionFor(typeof (RepositoryDecorator2<>),

                                                                        new InjectionConstructor(

                                                                            new ResolvedParameter(

                                                                                typeof (IRepository<>), "Decorator1")));

            container.Configure<InjectedMembers>().ConfigureInjectionFor(typeof(RepositoryDecorator1<>),

                                                                        new InjectionConstructor(

                                                                            new ResolvedParameter(

                                                                                typeof(IRepository<>), "Repository")));

            var repository = container.Resolve<IRepository<string>>();

            repository.Add("World.");

            Console.ReadLine();

 

        }

    }

 

    public interface IRepository<T>

    {

        void Add(T instance);

    }

 

    public class Repository<T> : IRepository<T>

    {

        public void Add(T instance)

        {

            Console.Write(instance);

        }

    }

 

    public class RepositoryDecorator1<T> : IRepository<T>

    {

        private readonly IRepository<T> _inner;

 

        public RepositoryDecorator1(IRepository<T> inner)

        {

            _inner = inner;

        }

 

        public void Add(T instance)

        {

            Console.Write(", ");

            _inner.Add(instance);

        }

    }

 

    public class RepositoryDecorator2<T> : IRepository<T>

    {

        private readonly IRepository<T> _inner;

 

        public RepositoryDecorator2(IRepository<T> inner)

        {

            _inner = inner;

        }

 

        public void Add(T instance)

        {

            Console.Write("Hello");

            _inner.Add(instance);

        }

    }

}



Regards,

Dave
Oct 18, 2008 at 2:54 AM
We took a look, and I'm afraid you'll have to talk to the FAA, since we have a case of pilot error. :-)

The issue is in your second call to ConfigureInjectionFor:

            container.Configure<InjectedMembers>().ConfigureInjectionFor(typeof(RepositoryDecorator1<>),

                                                                        new InjectionConstructor(

                                                                            new ResolvedParameter(

                                                                                typeof(IRepository<>), "Repository")));

Notice you missed supplying the name in the call. As such, you were configuring the default instance of RepositoryDecorator1<>, not the named "Decorator1" instance like you intended. So when it went to build up the named instance, it didn't have the specific constructor specified, used the default behavior, and BOOM.

You needed to do this:

            container.Configure<InjectedMembers>().ConfigureInjectionFor(typeof(RepositoryDecorator1<>), "Decorator1" // <--- add name here

                                                                        new InjectionConstructor(

                                                                            new ResolvedParameter(

                                                                                typeof(IRepository<>), "Repository")));

 

This is obviously less than optimal as an API design. It turns out that one of the changes we made in 1.2 makes this much harder to get wrong. Instead of calling ConfigureInjectionFor separately, you can configure injection directly in the RegisterType call. So you could have configured the container like this:

            IUnityContainer container = new UnityContainer();

            container.RegisterType(typeof (IRepository<>), typeof (RepositoryDecorator2<>),
                new InjectionConstructor(
                    new ResolvedParameter(typeof(IRepository<>), "Decorator1")));

            container.RegisterType(typeof (IRepository<>), typeof (RepositoryDecorator1<>),"Decorator1",
                new InjectionConstructor(
                    new ResolvedParameter(typeof(IRepository<>), "Repository")));

            container.RegisterType(typeof (IRepository<>), typeof (Repository<>),"Repository");

 

Much more succinct and impossible to mismatch the type/name pair as is possible in the old API.

-Chris

Oct 19, 2008 at 2:28 AM
Ahh.... I didn't realize I had to add the name. Now it works as expected.

Thanks for setting me straight, Chris!

Regards,

Dave