无法理解Service Locator实现中的引用类型/引用复制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了无法理解Service Locator实现中的引用类型/引用复制相关的知识,希望对你有一定的参考价值。

在实现服务定位器时,我遇到了一些我对引用类型感到困惑的事情。

在下面的代码中,我有一个静态类ServiceLocator,它暴露了2个静态方法,GetServiceProvideService - get返回当前服务,并提供一个新服务作为参数并将其分配给当前服务变量。如果提供的服务为null,则将currentService分配给在类声明开始时初始化的静态defaultService。简单的东西:

public static class ServiceLocator {
    private static readonly Service defaultService = new Service();
    private static Service currentService = defaultService;

    public static Service GetService() {
        return currentService;
    }

    public static void ProvideService(Service service) {
        currentService = service ?? defaultService;
    }
}

令我困惑的是:我有一个单独的类,它在名为currentService的变量中在类声明的开头存储对referenceToCurrentServiceAtStart的引用。当我为服务定位器提供一个新的Service实例来更新当前服务时,referenceToCurrentServiceAtStart会出现以保持对defaultService的引用:

public class ClassThatUsesService {
    private Service referenceToCurrentServiceAtStart = ServiceLocator.GetService();

    private static ClassThatUsesService() {
        ServiceLocator.ProvideService(new Service());
        // this variable appears to still reference the defaultService 
        referenceToCurrentServiceAtStart != ServiceLocator.GetService()
    }
}

所以引用似乎遵循这种链:

referenceToCurrentServiceAtStart -> defaultService -> (Service in memory)

这是可以理解的,因为referenceToCurrentServiceAtStart只是复制currentService参考。但是,我正在寻找/想要的行为是referenceToCurrentServiceAtStart总是引用任何currentService引用,所以它由Provide()更新。更类似于:

referenceToCurrentServiceAtStart -> currentService -> (Service> in memory)

那么,这种行为可能吗?我真的不确定我是如何实现这种参考行为的。我是C#的新手,所以很可能有一些明显的语言功能我无能为力。任何帮助将不胜感激。

答案

这种行为可能吗?

不,不像你描述的那样。正如您已经知道的那样,您获得的只是原始参考的副本。更改原始引用不会更改副本,只需将int变量的值复制到另一个变量就可以让以后更改原始引用并更改副本:

int original = 17;
int copy = original;

original = 19;
// "copy" is still 17, of course!

如果你想在ServiceLocator中总是拥有引用的当前值,那么你应该总是从该类中检索值,而不是使用本地字段。在上面的例子中,你可以间接通过一个属性,例如:

public class ClassThatUsesService {
    private Service referenceToCurrentServiceAtStart => ServiceLocator.GetService();
}

这是一个字符的变化(=变成=>),但不要被愚弄。这是实施方面的重大变化。你最终取而代之的是一个只读属性(即只有一个get方法而没有set方法),其中该属性的get方法调用ServiceLocator.GetService()方法并返回结果。

就个人而言,我不会打扰。除非你对referenceToCurrentServiceAtStart的实施将来会有一些非常强烈的期望,你应该直接调用ServiceLocator.GetService()。甚至没有referenceToCurrentServiceAtStart财产。由于代码总是希望获得当前值,因此确保获得当前值的最佳方法是直接从存储该值的类中获取当前值。

最后,我将借此机会展示一个类似于你所要求的场景,但并不完全如此。特别是,因为您试图将引用存储在类字段中,所以您需要执行上述操作。但是,最新的C#有"reference return values",必须存储在"ref locals"中。由于你想要引用一个保证永远存在的static字段,你实际上可以返回对该字段的引用,将其存储在本地,并且当你检索本地变量的值时,它将始终具有字段,因为它是对字段的引用,而不是它的副本。

您可以在文档中看到示例(请参阅上面的链接),但这是另一个与您正在做的更相似的示例:

class Program
{
    static void Main(string[] args)
    {
        // stores a reference to the value returned by M1(), which is to say,
        // a reference to the B._o field.
        ref A a1 = ref B.M1();

        // Keep the original value, and create a new A instance
        A original = a1, a2 = new A();

        // Update the B._o field to the new A instance
        B.M2(a2);

        // Check the current state
        Console.WriteLine($"original.ID: {original.ID}");
        Console.WriteLine($"a1.ID: {a1.ID}");
        Console.WriteLine($"a2.ID: {a2.ID}");
    }
}

class A
{
    private static int _id;

    public int ID { get; }

    public A()
    {
        ID = ++_id;
    }
}

class B
{
    private static A _o = new A();

    public static ref A M1()
    {
        // returns a _reference_ to the _o field, rather than a copy of its value
        return ref _o;
    }

    public static void M2(A o)
    {
        _o = o;
    }
}

当你运行上面的内容时,你会得到这个输出:

original.ID: 1
a1.ID: 2
a2.ID: 2

换句话说,变量a1会产生与a2中相同的值,B.M2()是传递给B._o方法以修改B._o字段的新对象,而ref字段值的原始副本仍然是对原始对象的引用字段引用。

这在你的情况下不起作用,因为返回的ref值必须存储在qazxswpoi本地。你不能把它放到一个类字段中。但它与你的场景类似,我想提及它,以防你想改变你的设计以允许它,或者想要在其他方案中使用该技术。

以上是关于无法理解Service Locator实现中的引用类型/引用复制的主要内容,如果未能解决你的问题,请参考以下文章

Atitit。如何实现dip, di ,ioc ,Service Locator的区别于联系

Service Locator 模式

Understanding Inversion of Control, Dependency Injection and Service Locator Print

SSH框架中POJO层, Dao层,Service层, Action层的功能理解

注解service和component的区别

C++Expression的学习笔记