Structuremap是否支持开箱即用的Lazy加载?

10

StructureMap是否允许您以延迟方式进行构造函数注入?也就是在使用对象之前不创建被注入的对象?


一个对象为什么会有一个未被使用的依赖关系? - Phill
1
好问题,也是一个有效的观点。如果类中并非所有方法都使用注入的对象,那么有些类可能会注入多个对象,但并不是每个对象都在每个方法中使用。这或许是一种“异味”? - Ryan Anderson
1
我认为一个不使用依赖的类很可能违反了单一职责原则。我不知道你的类在做什么,所以我不能评判,但这可能是一个尝试做两件事情的情况,也许是时候将它们分成不同的类了。话虽如此,如果只是一次性的,那么可能没有关系,因为对象的创建很容易,所以我怀疑目前性能不是什么问题。 - Phill
1
在至少一个方面上,我必须不同意Phil对SRP违规的看法。逻辑已经被拆分成多个类,这就是它们被注入的原因。想象一下你有几个依赖项,其中并非所有依赖项都会立即需要,并且有些可能根本不会被调用。构建这些依赖关系将是一种浪费,因此推迟到实际调用时再进行似乎对我来说是一个完全有效的决定。 - Mel
我认为你的说法是正确的Mel。我不希望我的调用程序需要了解太多,或者从UI层向业务层进行一系列“智能”调用。业务层封装的一些决策更多地基于最终用户的行为,以及执行该行为所需的规则和步骤可能有几个依赖项,UI不应该担心这些。 - Ryan Anderson
2个回答

13

更新: StructureMap v3已经直接实现了这个功能,所以这个技巧已经不再必要。


StructureMap版本2并没有直接提供这个功能,但是通过一些技巧,你可以让它实现你想要的功能。首先,你可以像下面这样手动设置Lazy<T>实例:

container = new Container(x =>
{
    x.Scan(y =>
    {
        y.TheCallingAssembly();
        y.WithDefaultConventions();
    });

    x.For<Lazy<IFoo>>().Use(y => new Lazy<IFoo>(y.GetInstance<Foo>));
    x.For<Lazy<IBar>>().Use(y => new Lazy<IBar>(y.GetInstance<Bar>));
    x.For<Lazy<IBaz>>().Use(y => new Lazy<IBaz>(y.GetInstance<Baz>));
});

这个方法可以正常工作,但是需要逐个注册每种类型。如果能够利用更加约定俗成的方法将会更好。理想情况下,以下语法更好。

x.For(typeof(Lazy<>)).Use(typeof(Lazy<>));

这种语法实际上可以工作... 但有所限制。不幸的是,在运行时,StructureMap将尝试找到最“贪婪”的Lazy<T>构造函数并选用 public Lazy(Func<T> valueFactory, bool isThreadSafe)。由于我们没有告诉它如何处理布尔型参数isThreadSafe,当它尝试解析`Lazy'时,它会抛出异常。

Lazy的文档说明默认的Lazy(Func<T> valueFactory)构造函数的线程安全模式是LazyThreadSafetyMode.ExecutionAndPublication,这恰好是通过将true传入上面构造函数的isThreadSafe参数获得的内容。因此,如果我们能够告诉StructureMap为isThreadSafe传递true,那么我们就能得到与我们一开始想要使用的构造函数(例如Lazy(Func<T> valueFactory))相同的行为。

简单地注册x.For(typeof(bool)).Use(y => true)非常鲁莽和危险,因为我们将告诉StructureMap在任何地方都使用值true,而不是为仅此一个布尔类型参数告诉StructureMap要使用哪个值,我们可以这样做。

x.For(typeof(Lazy<>)).Use(typeof(Lazy<>))
 .CtorDependency<bool>("isThreadSafe").Is(true);

当解析Lazy<T>时,StructureMap现在知道要使用"isThreadSafe"参数的值为"true"。我们现在可以在构造函数参数中使用Lazy<T>,并获得你想要的行为。

您可以在此处详细了解 Lazy类。


请注意:这段内容与我对问题6814961的回答大部分(90%)相同,但实际上更适合回答这个问题。 - Mel
你有没有一个链接可以解释StructureMap v3如何默认使用延迟加载?谢谢。 - evilpilaf
1
我不会说“默认情况下”。只是你不再需要欺骗它来做这件事了。在你的类的构造函数中,不要使用IFoo,而是使用Lazy<IFoo>。StructureMap不会实际构建Foo,直到你使用.Value检索它。我通常从构造函数中获取延迟依赖项,并将它们存储在后备变量中,然后通过像“private IFoo Foo { get { return _foo.Value; }}”这样的属性公开它们。 - Mel
你能展示一下现在如何默认支持Lazy吗? - Petrus Theron
我的意思是,您不需要再告诉StructureMap如何处理延迟依赖关系。在您的服务/控制器/任何其他东西的构造函数中,只需将依赖项键入Lazy<IThingService>而不是简单地键入IThingService即可。StructureMap应该会等到您实际使用它时才创建实例。 - Mel

5
是的,是这样的。StructureMap的最新版本(2.6.x)是针对.NET Framework 3.5编译的,因此无法访问.NET 4中引入的Lazy<T>类型。但是,它支持相同的功能 - “在使用时才创建被注入的对象”。你不再依赖于Lazy<T>,而是依赖于Func<T>。不需要特殊的容器注册。
我包含了一个示例程序,它会创建以下输出:
Created Consumer
Consuming
Created Helper
Helping

Sample.cs:

class Program
{
    static void Main(string[] args)
    {
        var container = new Container(x =>
        {
            x.For<IConsumer>().Use<Consumer>();
            x.For<IHelper>().Use<Helper>();
        });

        var consumer = container.GetInstance<IConsumer>();
        consumer.Consume();
    }
}

public class Consumer : IConsumer
{
    private readonly Func<IHelper> _helper;

    public Consumer(Func<IHelper> helper)
    {
        _helper = helper;
        Console.WriteLine("Created Consumer");
    }

    public void Consume()
    {
        Console.WriteLine("Consuming");
        _helper().Help();
    }
}

public interface IConsumer
{
    void Consume();
}

public interface IHelper
{
    void Help();
}

public class Helper : IHelper
{
    public Helper()
    {
        Console.WriteLine("Created Helper");
    }

    public void Help()
    {
        Console.WriteLine("Helping");
    }
}

我认为StructureMap支持小写的lazy,但不支持大写的Lazy。也许我在问题的大小写上读得太多了,但我认为这很重要。我还使用了Func<>技巧。它可以实现相同的结果,但需要您在每次使用时添加括号,这应该表明Func将在每次使用时执行。或者,您可以在Func的后面添加一个额外的属性,并执行类似"return _helper ?? (_helper = _helperFunc());"的操作。 - Mel
问题的意图似乎是第二个句子。用户想要延迟实例化对象。这就是我的回答。 - Joshua Flanagan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接