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

时间:2017-12-21 02:13:47

标签: c# reference 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;
    }
}

我感到困惑的是:我有一个单独的类,它在名为{{1}的变量中的类声明的开头存储对currentService的引用}。当我为服务定位器提供新的服务实例来更新当前服务时,会显示referenceToCurrentServiceAtStart以保持对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()

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

1 个答案:

答案 0 :(得分:1)

  

这种行为可能吗?

不,不像你描述的那样。正如您已经知道的那样,您获得的只是原始参考的副本。更改原始引用不会更改副本,只需将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方法调用{{ 1}}方法并返回结果。

就个人而言,我不会打扰。除非您非常期望ServiceLocator.GetService()的实施将来会发生变化,否则您应该直接致电referenceToCurrentServiceAtStart。甚至没有ServiceLocator.GetService()属性。由于代码期望始终获得当前值,因此确保始终获取当前值的最佳方法是直接从存储该值的类开始。

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

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

static

当您运行上述内容时,您将获得此输出:

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

换句话说,变量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; } } 最终会产生与a1中相同的值,这是传递给a2方法的新对象,用于修改B.M2()字段,而B._o字段值的原始副本仍然是对引用字段的原始对象的引用。

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