无法理解服务定位器实现中的引用类型/引用复制
Trouble understanding reference types / reference copying in Service Locator implementation
在实现服务定位器时,我遇到了一些我对引用类型感到困惑的事情。
在下面的代码中,我有一个静态 class ServiceLocator
公开了 2 个静态方法,GetService
和 ProvideService
- 获取 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 字段。但它与我想提及的你的场景非常相似,以防你想要更改你的设计以允许它,或者想要在其他确实以这种方式工作的场景中使用该技术。
在实现服务定位器时,我遇到了一些我对引用类型感到困惑的事情。
在下面的代码中,我有一个静态 class ServiceLocator
公开了 2 个静态方法,GetService
和 ProvideService
- 获取 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 字段。但它与我想提及的你的场景非常相似,以防你想要更改你的设计以允许它,或者想要在其他确实以这种方式工作的场景中使用该技术。