无法理解服务定位器实现中的引用类型/引用复制

Trouble understanding reference types / reference copying in Service Locator implementation

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

在下面的代码中,我有一个静态 class ServiceLocator 公开了 2 个静态方法,GetServiceProvideService - 获取 returns 当前service, provide 将一个新服务作为参数并将其分配给当前服务变量。如果提供的服务为空,它会将 currentService 分配给在 class 声明开始时初始化的静态 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;
    }
}

我感到困惑的是:我有一个单独的 class,它在 its 的开头存储对 currentService 的引用 class 在名为 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 引用。 然而,我正在寻找 for/would 的行为是 referenceToCurrentServiceAtStart 总是引用任何 currentService 引用,所以它由 Provide() 更新。更类似于:

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

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

is this behaviour possible?

不,不像你描述的那样。如您所知,您得到的只是原始参考资料的副本。更改原始引用不会更改副本,就像将 int 变量的值复制到另一个变量将允许您稍后更改原始引用并更改副本:

int original = 17;
int copy = original;

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

如果您希望在 ServiceLocator 中始终拥有引用的当前值,那么您应该始终从 class 中检索值,而不是使用本地字段。在上面的示例中,您可以通过 属性 间接访问,例如:

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

这是一个字符的变化(= 变成了 =>),但不要被愚弄了。这是实施中的 重大 变化。您最终得到的不是字段,而是只读的 属性(即只有 get 方法而没有 set 方法),其中 属性 get 方法调用 ServiceLocator.GetService() 方法并 return 结果。

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

最后,我将借此机会展示一个与您的要求类似但不完全相同的场景。特别是,因为您试图将引用存储在 class 字段中,所以上面是您需要执行的操作。但是,最新的 C# 有 "reference return values", which must be stored in "ref locals"。因为你想引用一个保证总是存在的 static 字段,你实际上可以 return 引用 字段 ,将其存储在本地,并且当您检索局部变量的值时,它将始终具有字段中的任何内容,因为它是对该字段的引用,而不是它的副本。

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

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() 方法以修改 [=28] 的新对象=] 字段,而 B._o 字段值的原始副本仍然是对该字段引用的原始对象的引用。

这在您的情况下不起作用,因为 returned 的 ref 值必须存储在 ref 本地。您不能将其放入 class 字段。但它与我想提及的你的场景非常相似,以防你想要更改你的设计以允许它,或者想要在其他确实以这种方式工作的场景中使用该技术。