【问题标题】:How can I make a read-only ObservableCollection property?如何创建只读的 ObservableCollection 属性?
【发布时间】:2009-11-19 14:33:38
【问题描述】:

我想在包含对象列表(来自数据库)的视图模型上公开一个属性。

我需要这个集合是只读的。也就是说,我想阻止添加/删除等。但允许 foreach 和索引器工作。我的意图是声明一个包含可编辑集合的私有字段并使用只读公共属性引用它。如下

public ObservableCollection<foo> CollectionOfFoo { 
     get { 
         return _CollectionOfFoo;
     }
}

但是,该语法只是防止更改对集合的引用。它不会阻止添加/删除等。

实现此目的的正确方法是什么?

【问题讨论】:

    标签: c# .net wpf observablecollection


    【解决方案1】:

    [previously] 接受的答案实际上会在每次访问 ReadOnlyFoo 时返回一个不同 ReadOnlyObservableCollection。这很浪费,并且可能导致细微的错误。

    一个更好的解决方案是:

    public class Source
    {
        Source()
        {
            m_collection = new ObservableCollection<int>();
            m_collectionReadOnly = new ReadOnlyObservableCollection<int>(m_collection);
        }
     
        public ReadOnlyObservableCollection<int> Items
        {
            get { return m_collectionReadOnly; }
        }
     
        readonly ObservableCollection<int> m_collection;
        readonly ReadOnlyObservableCollection<int> m_collectionReadOnly;
    }
    

    完整的讨论请参见ReadOnlyObservableCollection anti-pattern

    【讨论】:

    • 您可以通过使用私有设置器将Items 设为自动属性来消除对m_collectionReadOnly 的需求,并且只需在构造函数中设置一次。然后,您可以拥有更简洁的代码,并且您不必担心每次访问时都会创建一个新实例。
    • 现在你可以了,虽然我不确定在我写答案时是否有自动属性的私有设置器可用。但是,使用 readonly 会执行比您建议的更严格的合同。如果你有一个私有的设置器,其他类方法理论上仍然可以在对象的生命周期内改变值。虽然这在您今天编写的简单课程中似乎不太可能,但请记住,代码通常会存在多年,有许多维护者。出于这个原因,我赞成最不宽松的合同。
    • 使用 C#6,您现在还可以使用隐式只读设置器 ...{ get; },只能在构造函数中赋值。那你就两全其美了。
    【解决方案2】:

    使用ReadOnlyObservableCollection< T >

    public ReadOnlyObservableCollection<T> ReadOnlyFoo
    {
        get { return new ReadOnlyObservableCollection<T> (_CollectionOfFoo); }
    }
    

    正如已经指出的那样,请使用 Eric J 的答案,因为这个错误每次都返回一个新实例。

    【讨论】:

    • 注意认为 ReadOnlyObservableCollection 的 INotifyCollectionChanged 更改是受保护的,因此要访问事件连接,您需要显式转换为 INotifyCollectionChanged。另一种方法是扩展 ROOC 以公开该事件(它应该首先具有 imho)并提交有关当前实现的 MSDN 错误报告。 :)
    • @GaussZ 不错不错,为什么不公开?这对我来说毫无意义。我可能会将其提取到一个新的 SO 问题中,以查看其他人是否对此有所了解。
    • 正如@Eric J. 所指出的,每次访问属性时都返回一个新的 ReadOnlyObservableCollection,这使得客户端很难正确取消订阅事件(这可能是它是 ReadOnlyObservableCollection 的原因,而不仅仅是一个只读集合)。
    【解决方案3】:

    我不喜欢使用ReadOnlyObservableCollection&lt;T&gt;,因为这似乎是一个错误/损坏的课程;我更喜欢基于合同的方法。

    这是我使用的允许协方差的方法:

    public interface INotifyCollection<T> 
           : ICollection<T>, 
             INotifyCollectionChanged
    {}
    
    public interface IReadOnlyNotifyCollection<out T> 
           : IReadOnlyCollection<T>, 
             INotifyCollectionChanged
    {}
    
    public class NotifyCollection<T> 
           : ObservableCollection<T>, 
             INotifyCollection<T>, 
             IReadOnlyNotifyCollection<T>
    {}
    
    public class Program
    {
        private static void Main(string[] args)
        {
            var full = new NotifyCollection<string>();
            var readOnlyAccess = (IReadOnlyCollection<string>) full;
            var readOnlyNotifyOfChange = (IReadOnlyNotifyCollection<string>) full;
    
    
            //Covarience
            var readOnlyListWithChanges = 
                new List<IReadOnlyNotifyCollection<object>>()
                    {
                        new NotifyCollection<object>(),
                        new NotifyCollection<string>(),
                    };
        }
    }
    

    【讨论】:

    • 确实,ReadOnlyObservableCollection 的存在本身就是一种巨大的设计气味。 ObservableCollection 应该从一开始就实现了一个只读的组合视图接口。
    • @SørenBoisen 我同意ReadOnlyObservableCollection 应该是某种IReadOnlyObservableCollection 在interitance 层次结构中的某处
    • 非常漂亮,优雅。我喜欢这个比使用ReadOnlyObservableCollection&lt;T&gt; 类要好得多。也就是说,值得注意的是,使用ReadOnlyObservableCollection&lt;T&gt; 的一个优点是类型本身只是不支持 修改。使用此解决方案,您不会受到粗心的编码人员的保护,他们只是将实例转换回可写类型(即NotifyCollection&lt;T&gt;INotifyCollection&lt;T&gt;IList&lt;T&gt; 等)。相信我,在任何足够大的团队中,如果可以的话,至少会有一个人会这样做。
    【解决方案4】:

    您可以将属性的类型更改为 IEnumerable:

    public IEnumerable<foo> CollectionOfFoo { 
         get { 
             return _CollectionOfFoo;
         }
    }
    

    我认为没有公开索引器的标准接口。如果您需要它,您可以编写一个接口并扩展 ObservableCollection 来实现它:

    public interface IIndexerCollection<T> : IEnumerable<T>
    {
        T this[int i]
        {
            get;
        }
    }
    
    public class IndexCollection<T> : ObservableCollection<T>, IIndexerCollection<T>
    {
    }
    

    【讨论】:

    • 但请记住,如果 CollectionOfFoo 是一个 List,并且调用者知道这一点,它可以将 CollectionOfFoocast 返回到 List,然后添加/删除操作。这行得通,但它不是防弹的。
    • Binding to an ItemsSource。这个答案有效,但应该避免。
    • @jberger 链接已损坏
    • @LuckyLikey 尝试ItemsSource 示例部分
    【解决方案5】:

    您还可以覆盖您正在使用的列表类,并在其中一个构造函数中放置一个不可变标志,这样如果它是在将不可变标志设置为 true 的情况下构造的,它将不会添加/删除。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-10
    • 2021-10-04
    • 1970-01-01
    • 2021-05-27
    • 2014-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多