有没有办法让 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 实际上是代理对象)。如果您不介意启用优化的依赖性,StackFrameGetILOffset 可能会有所帮助,但可能会在调试模式下失败。而且很脆。

通过使用 dynamicExpando 可以在 Command 的类型中隐藏 Singleton 的使用,但这似乎是一个更糟糕的主意.