有没有办法让 C# 中的 getter 在线缓存其结果?
Is there a way for a getter in C# to cache its result in-line?
我一直在研究 Discord 机器人,我的一个抽象 类 代表服务器 ("Bot Contexts") 包含以下数据。
public virtual Command[] ContextSpecificCommands { get; } = null;
在用户定义的上下文中,我希望他们覆盖它(如果具有此代码的服务器需要)。然而,有一个问题是,我希望命令在上下文范围内是单例的。这意味着 CommandFoo
只能在 CoolContextNumberOne
中存在一次,也可以在 CoolContextNumberTwo
中存在(作为 CommandFoo
的单独实例),但单个上下文不能有两个实例其中 CommandFoo
个。
我的问题与 getter 的行为有关。如果用户这样做...
public override Command[] ContextSpecificCommands => new Command[] {
new CommandFoo()
};
然后这将实例化 CommandFoo
每次 ContextSpecificCommands 被引用。
有什么方法可以确保 ContextSpecificCommands
被内联缓存,以便它只实例化该容器数组一次?我想避免要求用户指定一个字段并尽可能指向该字段。
如果不添加额外的代码,这是不可能的。
为了缓存结果,必须创建一个单独的支持字段,并且必须设置代码以使用该字段。
为了解决我的困境,我修改了我的代码:
// User will override this.
public virtual Command[] ContextSpecificCommands { get; } = null;
收件人:
// User will override this.
protected virtual Command[] ContextSpecificCommands { get; } = null;
// These control the cache.
private bool HasPopulatedCommandCache = false;
private Command[] CommandCache = null;
// Things will reference this to get ahold of commands.
public Command[] Commands {
get {
if (!HasPopulatedCommandCache) {
HasPopulatedCommandCache = true;
CommandCache = ContextSpecificCommands;
}
return CommandCache;
}
}
这允许代码满足我在原始问题中指定的所有目标。用户的 class 可以使用内联表达式来定义他们的命令,而不必担心每次引用该数组时都会实例化它。
此代码为智力练习,不推荐!
如果您愿意强制 BotContexts
的实现者使用特定的形式,那么您可以在 属性 定义中插入一种单例模式。
考虑:
public static class Singleton {
public static T For<T>(Func<T> makeSingleton) => Singleton<T>.For(makeSingleton);
}
public static class Singleton<T> {
static Dictionary<Func<T>, T> Cache = new Dictionary<Func<T>, T>();
public static T For(Func<T> makeSingleton) {
T singleton;
if (!Cache.TryGetValue(makeSingleton, out singleton)) {
singleton = makeSingleton();
Cache[makeSingleton] = singleton;
}
return singleton;
}
}
现在你可以这样使用了:
public class CoolContextNumberOne : BotContexts {
public override Command[] ContextSpecificCommands => Singleton.For(() => new Command[] { new CommandFoo() });
}
public class CoolContextNumberTwo : BotContexts {
public override Command[] ContextSpecificCommands => Singleton.For(() => new Command[] { new CommandFoo() });
}
无论ContextSpecificCommands
被调用多少次,每个CoolContext都会创建一个CommandFoo
实例。
由于在 C# 中使用 new
表达式总是会生成一个新对象,因此很难(不可能?)了解如何让代码相同并确定何时生成新对象对象以及何时 return 现有对象(例如,如果 Command
实际上是代理对象)。如果您不介意启用优化的依赖性,StackFrame
和 GetILOffset
可能会有所帮助,但可能会在调试模式下失败。而且很脆。
通过使用 dynamic
和 Expando
可以在 Command
的类型中隐藏 Singleton
的使用,但这似乎是一个更糟糕的主意.
我一直在研究 Discord 机器人,我的一个抽象 类 代表服务器 ("Bot Contexts") 包含以下数据。
public virtual Command[] ContextSpecificCommands { get; } = null;
在用户定义的上下文中,我希望他们覆盖它(如果具有此代码的服务器需要)。然而,有一个问题是,我希望命令在上下文范围内是单例的。这意味着 CommandFoo
只能在 CoolContextNumberOne
中存在一次,也可以在 CoolContextNumberTwo
中存在(作为 CommandFoo
的单独实例),但单个上下文不能有两个实例其中 CommandFoo
个。
我的问题与 getter 的行为有关。如果用户这样做...
public override Command[] ContextSpecificCommands => new Command[] {
new CommandFoo()
};
然后这将实例化 CommandFoo
每次 ContextSpecificCommands 被引用。
有什么方法可以确保 ContextSpecificCommands
被内联缓存,以便它只实例化该容器数组一次?我想避免要求用户指定一个字段并尽可能指向该字段。
如果不添加额外的代码,这是不可能的。
为了缓存结果,必须创建一个单独的支持字段,并且必须设置代码以使用该字段。
为了解决我的困境,我修改了我的代码:
// User will override this.
public virtual Command[] ContextSpecificCommands { get; } = null;
收件人:
// User will override this.
protected virtual Command[] ContextSpecificCommands { get; } = null;
// These control the cache.
private bool HasPopulatedCommandCache = false;
private Command[] CommandCache = null;
// Things will reference this to get ahold of commands.
public Command[] Commands {
get {
if (!HasPopulatedCommandCache) {
HasPopulatedCommandCache = true;
CommandCache = ContextSpecificCommands;
}
return CommandCache;
}
}
这允许代码满足我在原始问题中指定的所有目标。用户的 class 可以使用内联表达式来定义他们的命令,而不必担心每次引用该数组时都会实例化它。
此代码为智力练习,不推荐!
如果您愿意强制 BotContexts
的实现者使用特定的形式,那么您可以在 属性 定义中插入一种单例模式。
考虑:
public static class Singleton {
public static T For<T>(Func<T> makeSingleton) => Singleton<T>.For(makeSingleton);
}
public static class Singleton<T> {
static Dictionary<Func<T>, T> Cache = new Dictionary<Func<T>, T>();
public static T For(Func<T> makeSingleton) {
T singleton;
if (!Cache.TryGetValue(makeSingleton, out singleton)) {
singleton = makeSingleton();
Cache[makeSingleton] = singleton;
}
return singleton;
}
}
现在你可以这样使用了:
public class CoolContextNumberOne : BotContexts {
public override Command[] ContextSpecificCommands => Singleton.For(() => new Command[] { new CommandFoo() });
}
public class CoolContextNumberTwo : BotContexts {
public override Command[] ContextSpecificCommands => Singleton.For(() => new Command[] { new CommandFoo() });
}
无论ContextSpecificCommands
被调用多少次,每个CoolContext都会创建一个CommandFoo
实例。
由于在 C# 中使用 new
表达式总是会生成一个新对象,因此很难(不可能?)了解如何让代码相同并确定何时生成新对象对象以及何时 return 现有对象(例如,如果 Command
实际上是代理对象)。如果您不介意启用优化的依赖性,StackFrame
和 GetILOffset
可能会有所帮助,但可能会在调试模式下失败。而且很脆。
通过使用 dynamic
和 Expando
可以在 Command
的类型中隐藏 Singleton
的使用,但这似乎是一个更糟糕的主意.