在实施服务定位器时,我遇到了一些我对参考类型感到困惑的事情。
在下面的代码中,我有一个静态类ServiceLocator
,它公开了2个静态方法,GetService
和ProvideService
- 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#的新手,所以很可能有一些明显的语言功能我无能为力。任何帮助将不胜感激。
答案 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
本地。你不能把它放到一个类字段中。但它与你的场景类似,我想提及它,以防你想改变你的设计以允许它,或者想要在其他方案中使用该技术以便以这种方式工作。