【问题标题】:Are .Net property setters ever called implicitly?.Net 属性设置器是否曾被隐式调用?
【发布时间】:2011-01-09 14:36:54
【问题描述】:

我正在使用 C# 编写一个 ASP.Net 2.0 项目。我有一些数据存储在会话状态中。为了方便使用,它被包裹在一个属性中,像这样:

protected IList<Stuff> RelevantSessionData
{
    get
    {
        return (IList<Stuff>) Session["relevant_key"];
    }
    set
    {
        Session["relevant_key"] = value;
    }
}

获取和设置值的工作方式完全符合您的预期。如果我想清除该值,我只是将它设置为null,并且没有问题。但是,在另一个开发人员的页面中,他调用了集合的 Clear() 方法。我认为这将是一个错误,但它似乎工作,我不明白为什么。它是这样工作的:

Debug.WriteLine(RelevantSessionData.Count);    //outputs, say, 3
RelevantSessionData.Clear();
Debug.WriteLine(RelevantSessionData.Count);    //outputs 0

为什么会这样?我天真的期望是中间行从会话加载序列化值,反序列化为一个对象,在该对象上调用Clear(),然后让未命名的对象超出范围。那将是一个错误,因为存储在 Session 中的值将保持不变。但显然,调用属性设置器并将新更改的集合序列化回会话中已经足够聪明了。

这让我有点紧张,因为在我们的遗留代码中有些地方属性设置器有副作用,如果不是有意的,我不希望它们被调用。

在这种情况下是否总是调用属性设置器?有其他事情发生吗?还是我完全误解了这里发生的事情?

[添加解释答案]
原来是误会了。我知道存储在 Session 中的对象必须是可序列化的,并且基于此我对集合内部的行为做了太多假设。我想多了。

存储对象只有一个实例(我的IList)。对 getter 的每次调用都会返回对同一实例的引用。所以上面引用的代码就像它看起来一样工作,不需要特殊的魔法。

并回答标题问题:不,setter 不会被隐式调用。

【问题讨论】:

    标签: c# .net asp.net session properties


    【解决方案1】:

    您可以期望在以下情况下调用属性设置器:

    • 这些是公开可见的(对其他程序集可见)。
    • 他们将 setter 实现为对其他程序集可见的接口的一部分。在某些情况下,例如
    • 它们用于 WPF 绑定(但框架将遵循有关 BindingMode 的规则)。
    • 它们在 MEF 中与 ImportAttribute 一起使用。
    • 它们用于其他一些绑定框架(你懂的)。

    对于别人定义的接口,如果你满足操作的前置条件和后置条件,你应该不会遇到问题。

    编辑:我同意上述观点。我公开收藏的首选是:

    private readonly List<T> _sources = new List<T>();
    
    /* Or ICollection<T>, ReadOnlyCollection<T>, or IList<T>, or
     * (only a real option for `internal` types) List<T>
     */
    public IEnumerable<T> Sources
    {
        get
        {
            return _sources;
        }
    }
    

    如果您绝对必须在对象创建后初始化列表,那么您可以使用类似这样的方法作为第二个选项:

    public IList<T> Sources
    {
        get;
        private set;
    }
    

    在某些情况下,上述做法不一定是最佳答案,但这是最常见的两种(IMO?)。

    【讨论】:

    • -1,答案完全脱离了原帖的主题。 OP 询问为什么给定的代码可以工作,以及由于重新创建的对象,他是否可以预料到一个错误。
    【解决方案2】:

    你的代码大致相当于:

    Debug.WriteLine(((IList<Stuff>) Session["relevant_key"]).Count);    //outputs, say, 3
    ((IList<Stuff>) Session["relevant_key"]).Clear();
    Debug.WriteLine(((IList<Stuff>) Session["relevant_key"]).Count);    //outputs 0
    

    即使你只调用 getter,你也在清除集合。所以调试输出看起来很正常。

    【讨论】:

    • +1。事实上,集合属性的最佳实践是将它们设为只读(根本没有设置器)。
    • 我认为你错过了我的观点。我知道属性访问器的作用,但我的错误是假设 HttpSessionState 在每次访问时都会序列化/反序列化。
    • 我错过了序列化/反序列化部分。我应该更仔细地阅读这个问题。
    【解决方案3】:

    是的,你是对的,如果你的 setter/getter 正在序列化/反序列化对象,这将是一个错误。但这种情况并非如此。相反,您是根据参考传递的。

    所以基本上发生的情况是,示例中的第一行通过 get 获取项目,并基于此调用 Count。然后第二行出去,再次调用get,返回同一个对象,运行clear,然后第三行和第一行一样。

    如果您编写了类似这样的 setter/getter,您将遇到“错误”

    protected IList<Stuff> RelevantSessionData
    {
        get
        {
            return (IList<Stuff>) JSON.ConvertFromString(Session["relevant_key"]);
        }
        set
        {
            Session["relevant_key"] = JSON.ConvertToString(value);
        }
    }
    

    在这种情况下,每次调用 get 块都会创建一个新对象。但是由于您上面的示例只是传递对同一对象的引用,因此您不会看到这个“错误”。

    我说“错误”是因为它不是真正的错误,它只是对幕后发生的事情的误解。

    我希望这会有所帮助。

    【讨论】:

    • 啊哈!我曾假设因为 Session 中的值必须是可序列化的,所以它们将以序列化状态存储,并且每个 get 都会返回不同的引用。如果不是这样,并且它足够聪明,可以在多次调用中返回相同的引用,那么我只是想多了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-11
    • 1970-01-01
    • 2018-10-01
    • 2011-12-16
    • 2010-10-08
    • 1970-01-01
    相关资源
    最近更新 更多