首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在C#中封装集合

在C#中封装集合
EN

Stack Overflow用户
提问于 2011-09-16 12:04:44
回答 4查看 7K关注 0票数 23

自3.0以来,C#具有很好的语法糖,如自动属性,这大大简化了封装原理的实现。如果将它与原子值一起使用,这是很好的,因此您可以替换如下封装模式:

代码语言:javascript
运行
复制
private string _name;

public string Name 
{
  get { return _name; }
  set { _name = value; }
}

只有一行:

代码语言:javascript
运行
复制
public string FirstName  { get; set; }

我非常喜欢这个伟大的特性,因为它节省了很多开发人员的时间。

但是,当您创建指向集合的属性时,情况就不太好了。通常,我看到集合属性是以两种方式之一实现的。

( 1)在没有自动属性的情况下,可以使用字段初始化器:

代码语言:javascript
运行
复制
private List<string> _names = new List<string>();

public List<string> Names
{
    get { return _names; }
}

( 2)使用汽车特性。如果类只有一个构造函数,则此方法是可以的:

代码语言:javascript
运行
复制
public List<string> Names { get; private set; }

public .ctor()
{
    Names = new List<string>();
}

但是,当您以这种方式处理诸如lists这样的可变集合时,就会破坏封装,因为该属性的用户可以修改集合而不让容器知道(如果忘记让setter私有,甚至可以替换集合)。

至于我,关于封装集合模式的正确实现,集合封装应该如下所示:

代码语言:javascript
运行
复制
private readonly List<string> _names = new List<string>();

public ICollection<string> Names
{
    get { return new ReadOnlyCollection<string>(_names); }
}

public void Add_Name(string name)
{
    _names.Add(name);
}

public void Remove_Names(string name)
{
    _names.Remove(name);
}

public void Clear_Names()
{
    _names.Clear();
}

老实说,我不记得我是否在真正的代码中遇到过这种实现,甚至在框架源代码中也是如此。我认为这是因为人们懒惰,避免编写这么多的代码,只是为了使封装更强大一点。

我想知道为什么C#团队没有提供一些清晰而简单的方法来定义集合自动属性,这样开发人员就可以满足他们的懒惰,仍然创建健壮的代码?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-09-16 13:47:50

TL;DR,C#编译器没有自动集合,因为公开集合有很多不同的方式。在公开集合时,应仔细考虑如何封装集合并使用正确的方法。

C#编译器提供自动属性的原因是,它们是常见的,并且几乎总是以相同的方式工作,但是,当您发现处理集合时情况很少那么简单时,有许多不同的公开集合的方法,正确的方法总是取决于情况,举几个例子:

1)可以更改的集合

通常没有必要对公开藏品施加任何真正的限制:

代码语言:javascript
运行
复制
public List<T> Collection
{
    get
    {
        return this.collection;
    }
    set
    {
        if (value == null)
        {
            throw new ArgumentNullException();
        }
        this.collection = value;
    }
}
private List<T> collection = new List<T>();

确保集合永远不为空可能是个好主意,否则您可以只使用自动属性。除非我有充分的理由想要更多地封装我的集合,否则我总是为了简单而使用这个方法。

2)可以修改但不能交换的集合

您可以用任何方式对其进行编码,但想法是相同的--公开集合允许修改项,但底层集合本身不能被其他集合替换。例如:

代码语言:javascript
运行
复制
public IList<T> Collection
{
    get
    {
        return this.collection;
    }
}
private ObservableCollection<T> collection = new ObservableCollection<T>();

当使用者应该能够修改集合时,我倾向于在处理诸如可观察收藏这样的事情时使用这种简单的模式,但是我已经订阅了更改通知--如果允许消费者交换整个集合,那么只会引起头痛。

3)公开集合的只读副本。

通常,您希望阻止使用者修改公开的集合--通常是做的,希望暴露类能够修改集合。要做到这一点,一种简单的方法是公开集合的只读副本:

代码语言:javascript
运行
复制
public ReadOnlyCollection<T> Collection
{
    get
    {
        return new ReadOnlyCollection<T>(this.collection);
    }
}
private List<T> collection = new List<T>();

这与返回的集合永不更改的属性相关,即使基础集合发生了更改。这通常是一件好事,因为它允许消费者迭代返回的集合,而不必担心它可能会被更改:

代码语言:javascript
运行
复制
foreach (var item in MyClass.Collection)
{
    // This is safe - even if MyClass changes the underlying collection
    // we won't be affected as we are working with a copy
}

然而,这并不总是预期的(或期望的)行为--例如,属性不是这样工作的。您还应该考虑到,以这种方式复制许多大型集合可能效率低下。

公开仅读取的集合时,始终要注意控件中的项仍然可以修改。同样,这可能是一件好事,但是如果您希望公开的集合“完全”不可修改,那么确保集合中的项也是只读/不可变的(例如,System.String)。

4)可以修改的集合,但只能以某种方式修改

假设您想公开一个项可以添加但不删除的集合?您可以公开公开类本身上的属性:

代码语言:javascript
运行
复制
public ReadOnlyCollection<T> Collection
{
    get
    {
        return new ReadOnlyCollection<T>(this.collection);
    }
}
private List<T> collection = new List<T>();

public AddItem(T item);

但是,如果您的对象有许多这样的集合,那么您的界面就会很快变得混乱和混乱。此外,我发现这种模式有时可能会违反直觉:

代码语言:javascript
运行
复制
var collection = MyClass.Collection;
int count = collection.Count;

MyClass.AddItem(item);

Debug.Assert(collection.Count > count, "huh?");

这需要付出更多的努力,但IMO的一个更整洁的方法是公开一个自定义集合,该集合封装了您的“真实”集合和关于如何更改集合的规则,例如:

代码语言:javascript
运行
复制
public sealed class CustomCollection<T> : IList<T>
{
    private IList<T> wrappedCollection;

    public CustomCollection(IList<T> wrappedCollection)
    {
        if (wrappedCollection == null)
        {
            throw new ArgumentNullException("wrappedCollection");
        }
        this.wrappedCollection = wrappedCollection;
    }

    // "hide" methods that don't make sense by explicitly implementing them and
    // throwing a NotSupportedException
    void IList<T>.RemoveAt(int index)
    {
        throw new NotSupportedException();
    }

    // Implement methods that do make sense by passing the call to the wrapped collection
    public void Add(T item)
    {
        this.wrappedCollection.Add(item);
    }
}

示例使用:

代码语言:javascript
运行
复制
public MyClass()
{
    this.wrappedCollection = new CustomCollection<T>(this.collection)
}

public CustomCollection<T> Collection
{
    get
    {
        return this.wrappedCollection;
    }
}
private CustomCollection<T> wrappedCollection;
private List<T> collection = new List<T>();

公开的集合现在封装了我们关于集合如何能够和不能被修改的规则,并且立即反映了对底层集合所做的更改(这可能是一件好事,也可能不是好事)。对于大型藏品来说,它也可能更有效率。

票数 21
EN

Stack Overflow用户

发布于 2011-09-16 12:15:04

代码语言:javascript
运行
复制
private IList<string> _list = new List<string>();

public IEnumerable<string> List
{
  get
  {
    ///return _list;
     return _list.ToList();
  }
}
票数 5
EN

Stack Overflow用户

发布于 2011-09-16 12:13:30

公开ICollection<string>不是一个好主意,因为它允许添加和删除操作。

代码语言:javascript
运行
复制
 public sealed class CollectionHolderSample
    {
        private readonly List<string> names;

        public CollectionHolderSample()
        {
            this.names = new List<string>();
        }

        public ReadOnlyCollection<string> Items
        {
            get
            {
                return this.names;
            }
        }

        public void AddItem(string item)
        {            
            this.names.Add(item);
        }
    }

编辑:

正如您所提到的,来自显式加()实现的移除()ReadOnlyCollection<T>.ICollection<T>方法将抛出NotSupportedException异常,因此集合是只读的。

此外,为了确保隐藏下的集合是只读的,您可以检查IsReadOnly性质

MSDN说:

如果ReadOnlyCollection.ICollection.IsReadOnly属性为 true,则为只读;否则为false。在ReadOnlyCollection的默认实现中,此属性始终返回true。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7444395

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档