IInstanceLookup context visibility

22 views
Skip to first unread message

mr.su...@gmail.com

unread,
Dec 21, 2014, 8:21:15 PM12/21/14
to aut...@googlegroups.com
Hi,

I recently posted a stack overflow question about how to pass delegate factory parameters to nested components. http://stackoverflow.com/questions/27497640/can-autofac-delegate-factories-pass-parameters-to-nested-objects

If nobody posts a better solution I have something simple that works by using OnPreparing to append parameters, but relies on reflection to access InstanceLookup._context & ResolveOperation._activationStack.

How does the group feel about the general concept of making information about enclosing resolve operations available through IInstanceLookup? (If I were to create a pull request.)

Should IInstanceLookup & IResolveOperation expose public properties for _context & _activationStack? Or should IInstanceLookup have a EnclosingInstanceLookup property that could be recursively enumerated?

Cheers,
Matt

Travis Illig

unread,
Dec 22, 2014, 11:57:49 AM12/22/14
to aut...@googlegroups.com, mr.su...@gmail.com
Hey, Matt. Thanks for jumping in here.

You'll probably remember me from the comments on that StackOverflow question. I'm one of the two current Autofac owners.

I'd be curious what Alex (the other project owner) thinks, but I'm still pretty strongly of the mind that this sort of thing should either be rectified by some redesign/metadata/keyed service work or a manually created factory.

There's a lot to consider when you start passing parameters down more than one level.

For example, say you have a system where you have ClassA, ClassB, and ClassC.

public class ClassA
{
  public ClassA(ClassB b, int aNumber) { /* */ }
}

public class ClassB
{
  public ClassB(ClassC c) { /* */ }
}

public class ClassC
{
  public ClassC(int cNumber)
}

If you let parameters pass sort of "globally" down the stack, then both the incarnations of ClassA AND ClassC would get the same number. Is that what the dev wants or not? How would they know? When debugging, where did that number come from? Is that something else that now needs to be configurable - parameter precedence order?

Then there's the notion of refactoring - what happens if you refactor ClassA to no longer need the int, but ClassC still does? Sounds OK, so you also refactor the factory delegate to no longer need that parameter (since it's two levels removed from what you're actually trying to resolve). Oh, wait, things are broken now because ClassC can no longer be resolved.

At first glance it sounds like something simple to want to add, but there are a lot of ramifications and a lot of ways people can shoot themselves in the foot.

It seems to be fairly rare for other containers to allow this sort of thing, too. Unity doesn't. Ninject appears to, but it sounds like there are mixed opinions on whether this should be allowed ("In general it's the wrong approach...").

If we did pull something like this in (and, again, I'm not sure that's a good idea), it'd be nice to add it in a similar way to how Ninject did it, where you mark a Parameter as "inherited" (or not) and have the resolution stack automatically deal with it internally in the standard parameter processing mechanism. We'd want to make sure the generated factory (Func<A, B>) behavior continued to work correctly and possibly integrated here somehow (not sure how). Delegate factories, like in your question, would need to be made to work. We'd have to consider the configuration around that integration because some folks would expect the default behavior (don't pass the params down) and others would want it, so figuring out at the generated factory level how to turn that on or off would be interesting. Anyway, all that would be done ideally without changing the public interface of low-level internals much since that can be a breaking change in interesting and unfortunate ways, particularly to people who have written more advanced extensions.

In any case, that's my two cents.
-T

Matt Sullivan

unread,
Dec 22, 2014, 9:19:38 PM12/22/14
to Travis Illig, aut...@googlegroups.com
Hi Travis,

Thank you for considering this. After commenting on my stack overflow post, I was hoping you would reply over here.

I understand and share your concerns. I personally would be very reluctant to resolve primitives by type without some associated metadata. I think resolving by (descriptive) parameter names should be easier to follow, but it is still easily broken when refactoring. Another problem is that it may often be clearer if factory parameter names were different to constructor parameter names. In the question I posted ShareHoldingFactory & WebQuoteService take a Uri 'source', but it would probably be better if the factory parameter was called something like 'webQuoteSource'.

Despite those issues, I'd still like to think Autofac can make this scenario easier. As I commented on SO, I think of the delegate as an interface to create a subsystem and I think it's awesome that autofac automatically provides a factory to build it. Yet if you want parameters deeper than the root component, you have to write the boilerplate manually. Do you not think this is a worthwhile Autofac use case?

I can see you've started to think about how to implement this automatically through the whole resolution stack. While less integrated, one benefit of my proposal is that users must modify OnPreparing of the component that needs the parameters. Do you think there's any merit in a design that requires the component registration to explicitly ask for parameters? Perhaps that's enough for the minority of users who need this.

Cheers,
Matt

Alex Meyer-Gleaves

unread,
Dec 22, 2014, 9:56:29 PM12/22/14
to aut...@googlegroups.com, travis...@gmail.com, mr.su...@gmail.com
Hi all,

I personally don't like the idea of having parameters passed through the component chain like that. This is not something that I think is worth the added complexity given that you can easily register a factory delegate (or Func signature) to represent the creation of the parent and child components.

public delegate Shareholding ShareHoldingFactory(string symbol, uint holding, Uri source);

builder.Register<ShareHoldingFactory>(ctx => 
{
var c = ctx.Resolve<ILifetimeScope>();
return (symbol, holding, source) => new Shareholding(
symbol, holding, c.Resolve<IQuoteService>(TypedParameter.From(source)));
});

var factory = container.Resolve<ShareHoldingFactory>();

var instance = factory("MSFT", 123u, new Uri("http://localhost/foo"));

This makes the entry point interface explicit and really isn't that hard to code. There are no doubt other ways to restructure the code so that you don't even need the factory. For example, the quote service could be registered using a lambda that grabs the source from configuration or the current environment instead of having to have it provided.

Cheers,

Alex.
Reply all
Reply to author
Forward
0 new messages