Closed BinaryCraX closed 2 years ago
@BinaryCraX I get it. There was a similar case before...
In principle, what you are expecting is the temporal coupling - if you change the order of resolution, mercedes.GetEngine();
before the call to factory, it will fail. If you run it in parallel depends on how lucky are you.
In order to enable such scenario, I need to check the runtime state (what are already created instances in scope) during the graph generation. Then I won't be able to cache or manipulate the expression freely. It is just a one-of-spending for the specific runtime state.
So, I won't do that.
But if you really need it, you may change the code for that. Here is the modified test part.
public Mercedes(Lazy<Engine> engineLazy, Func<int, Engine> engineFactory, IResolverContext rc)
{
this.getEngine = engineLazy;
this.EngineFactory = n => {
var e = engineFactory(n);
rc.Use(e); // explicitly make the resolved instance available as runtime state
return e;
};
}
You are right, there is some temporal coupling code smell in the sample code.
I did some more experimenting and realized, that the problem isn't with the Lazy-wrapper. Boiled down example:
[TestFixture]
class Resolve_after_explicit_create_with_scope
{
[Test]
public void Example()
{
var container = new Container();
container.Register<Engine>(Reuse.Scoped);
using (var scope = container.OpenScope())
{
var engine = scope.Resolve<Engine>(new object[] { 120 });
var sameEngine = scope.Resolve<Engine>(new object[] { 99 }); // expected to get the same instance of the engine (with maxRpm = 120). Works like expected.
Assert.AreEqual(engine, sameEngine);
var sameEngine2 = scope.Resolve<Engine>(); // expected to get the same instance of the engine. Doesn't work like expected: will crash because parameter can't be resolved.
Assert.AreEqual(engine, sameEngine2);
}
}
class Engine
{
public Engine(int maxRpm)
{
MaxRpm = maxRpm;
}
public int MaxRpm { get; }
}
}
Sadly I can't remove the temporal coupling completly because in my use case I know a parameter only at runtime:
@BinaryCraX
DryIoc has Use
method to inject values to scopes at runtime. So instead of using Func
try this:
using System;
using DryIoc;
public class Program
{
public static void Main()
{
var container = new Container();
container.Register<Engine>(Reuse.Scoped);
using (var scope = container.OpenScope())
{
scope.Use(120); // set the runtime data to scope
var engine = scope.Resolve<Engine>(); // new object[] { 120 }); - no need for passing args
var sameEngine = scope.Resolve<Engine>(); //new object[] { 99 }); // expected to get the same instance of the engine (with maxRpm = 120). Works like expected.
Console.WriteLine((engine == sameEngine) + ", maxRpm = " + engine.MaxRpm);
var sameEngine2 = scope.Resolve<Engine>(); // expected to get the same instance of the engine. Doesn't work like expected: will crash because parameter can't be resolved.
Console.WriteLine((engine == sameEngine2) + ", maxRpm = " + engine.MaxRpm);
}
}
class Engine
{
public Engine(int maxRpm)
{
MaxRpm = maxRpm;
}
public int MaxRpm { get; }
}
}
Ok, I got it now. This is sufficient for my use case. Thank you very much for your support.
When lazy-resolving an service after it has been explicitly created using an factory-func from within an scope it will throw an UnableToResolveUnknownService exception for the parameter that was previously provided using the factory-func-call. Because the instance was already created inside the scope using the factory-func I wouldn't expect this to happen, because it doesn't need to be created at this point anymore. I would expect, that a call to Lazy.Value just resolves the previously created instance and returns it.
Somehow when doing the same thing without an scope (but with singletons instead) everything works like expected.