Mar*_*ier 59 .net c# ienumerable encapsulation list
我花了好几个小时思考暴露名单成员的主题.在与我的类似问题中,John Skeet给出了一个很好的答案.请随便看看.
ReadOnlyCollection或IEnumerable用于公开成员集合?
我通常非常偏执暴露列表,特别是如果您正在开发API.
我一直使用IEnumerable来公开列表,因为它非常安全,而且它提供了很大的灵活性.我在这里举一个例子
public class Activity
{
private readonly IList<WorkItem> workItems = new List<WorkItem>();
public string Name { get; set; }
public IEnumerable<WorkItem> WorkItems
{
get
{
return this.workItems;
}
}
public void AddWorkItem(WorkItem workItem)
{
this.workItems.Add(workItem);
}
}
Run Code Online (Sandbox Code Playgroud)
任何针对IEnumerable进行编码的人都非常安全.如果我后来决定使用有序列表或其他东西,它们的代码都没有中断,它仍然很好.这样做的缺点是IEnumerable可以被强制转换回这个类之外的列表.
出于这个原因,许多开发人员使用ReadOnlyCollection来公开成员.这是非常安全的,因为它永远不会被强制转换为列表.对我来说,我更喜欢IEnumerable,因为它提供了更大的灵活性,我是否想要实现与列表不同的东西.
我想出了一个我更喜欢的新想法.使用IReadOnlyCollection
public class Activity
{
private readonly IList<WorkItem> workItems = new List<WorkItem>();
public string Name { get; set; }
public IReadOnlyCollection<WorkItem> WorkItems
{
get
{
return new ReadOnlyCollection<WorkItem>(this.workItems);
}
}
public void AddWorkItem(WorkItem workItem)
{
this.workItems.Add(workItem);
}
}
Run Code Online (Sandbox Code Playgroud)
我觉得这保留了IEnumerable的一些灵活性,并且封装得非常好.
我发布了这个问题,以便对我的想法有所了解.你更喜欢IEnumerable这个解决方案吗?你认为使用ReadOnlyCollection的具体返回值更好吗?这是一个相当大的争论,我想试着看看我们都能提出的优点/缺点是什么.
提前感谢您的意见.
编辑
首先,感谢大家为此次讨论做出的贡献.我确实从每一个人那里学到了很多东西,并真诚地感谢你.
我正在添加一些额外的场景和信息.
IReadOnlyCollection和IEnumerable存在一些常见的陷阱.
考虑以下示例:
public IReadOnlyCollection<WorkItem> WorkItems
{
get
{
return this.workItems;
}
}
Run Code Online (Sandbox Code Playgroud)
即使接口是只读的,上面的示例也可以返回到列表并进行变异.界面,尽管它是同名的,但并没有增加不变性.它由你来提供一个不可变的解决方案,因此你应该返回一个新的ReadOnlyCollection.通过创建一个新列表(本质上是一个副本),您的对象的状态是安全的.
Richiban在他的评论中说得最好:界面只能保证某些东西可以做什么,而不是它不能做什么
请参阅下面的示例.
public IEnumerable<WorkItem> WorkItems
{
get
{
return new List<WorkItem>(this.workItems);
}
}
Run Code Online (Sandbox Code Playgroud)
上面的内容可以被渲染和变异,但你的对象仍然是不可变的.
另外一个框外声明将是集合类.考虑以下:
public class Bar : IEnumerable<string>
{
private List<string> foo;
public Bar()
{
this.foo = new List<string> { "123", "456" };
}
public IEnumerator<string> GetEnumerator()
{
return this.foo.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
Run Code Online (Sandbox Code Playgroud)
上面的类可以有按照你想要的方式改变foo的方法,但你的对象永远不能被转换为任何排序和变异的列表.
CarstenFührmann对IEnumerables中的收益率报表提出了一个很好的观点.
再次感谢大家.
Car*_*ann 68
到目前为止,答案中似乎缺少一个重要方面:
当a IEnumerable<T>返回给调用者时,他们必须考虑返回的对象是"懒惰流"的可能性,例如使用"yield return"构建的集合.也就是说IEnumerable<T>,对于IEnumerable的每次使用,可能必须由调用者支付产生元素的性能损失.(生产力工具"Resharper"实际上将其指出为代码气味.)
相比之下,IReadOnlyCollection<T>向呼叫者发出的信号是没有懒惰的评估.(该Count属性,而不是在Count 扩展方法的IEnumerable<T>(这是由遗传IReadOnlyCollection<T>因此它具有方法为好),信号的非lazyness.等确实存在似乎是IReadOnlyCollection不懒惰实现的事实).
这对于输入参数也是有效的,因为请求IReadOnlyCollection<T>代替IEnumerable<T>该方法需要在集合上多次迭代的信号.当然,该方法可以从中创建自己的列表IEnumerable<T>并对其进行迭代,但由于调用者可能已经有一个已加载的集合,因此尽可能利用它是有意义的.如果调用者只有一个IEnumerable<T>,他只需要添加.ToArray()或.ToList()参数.
什么IReadOnlyCollection也不会做的是防止来电投其他一些集合类型.为了这种保护,人们必须使用该类 ReadOnlyCollection<T>.
总之,只有事情IReadOnlyCollection<T>确实相对IEnumerable<T>的就是添加一个Count属性,从而指示没有lazyness参与.
Mic*_*nny 18
谈到类库,我认为IReadOnly*非常有用,我认为你做得对:)
这一切都是关于不可变的集合...之前只有不可变的和放大数组是一项艰巨的任务,所以.net决定在框架中包含一些不同的,可变的集合,为你实现丑陋的东西,但恕我直言他们没有'给你一个非常有用的不可变方向,特别是在高并发场景中,共享可变东西总是PITA.
如果您今天检查其他语言,例如objective-c,您会看到实际上规则完全颠倒了!他们总是在不同的类之间交换不可变的集合,换句话说,接口公开只是不可变的,并且在内部他们使用可变集合(是的,他们当然有它),而是他们暴露正确的方法,如果他们想让外人改变集合(如果班级是有状态的班级).
所以我用其他语言获得的这种小经验促使我认为.net列表是如此强大,但不可变的集合出于某种原因:)
在这种情况下,不是帮助接口的调用者,以避免他改变所有代码,如果你改变内部实现,就像IList vs List一样,但是使用IReadOnly*你保护自己,你的类,以不正确的方式使用,以避免无用的保护代码,有时你不能写的代码(在过去的某些代码中我必须返回完整列表的克隆以避免这个问题) .
use*_*740 15
我对铸造和 IReadOnly* 合约及其“正确”用法的看法。
\n如果某些代码 \xe2\x80\x9cclever\xe2\x80\x9d 足以执行显式转换并破坏接口契约,那么 \xe2\x80\x9cclever\xe2\x80\x9d 也足以使用反射或否则会做一些邪恶的事情,例如访问 ReadOnlyCollection包装对象的基础列表。我不针对这样的\xe2\x80\x9cclever\xe2\x80\x9d 程序员进行\xe2\x80\x99t 编程。
\n我唯一保证的是,在公开所述 IReadOnly* 接口对象之后,我的代码将不会违反该约定,也不会修改返回的集合对象。
\n这意味着我编写的代码返回 List-as-IReadOnly*,例如,很少选择实际的只读具体类型或包装器。使用 IEnumerable.ToList足以返回 IReadOnly[List|Collection] - 调用 List.AsReadOnly 对于仍然可以访问 ReadOnlyCollection 包装的基础列表的 \xe2\x80\x9cclever\xe2\x80\x9d 程序员来说几乎没有什么价值。
\n在所有情况下,我保证 IReadOnly* 返回值的具体类型是渴望的。如果我曾经编写一个返回 IEnumerable 的方法,那是因为该方法的约定是 \xe2\x80\x9c 支持流式传输\xe2\x80\x9d fsvo。
\n就 IReadOnlyList 和 IReadOnlyCollection 而言,当存在对索引有意义的隐含稳定排序时,我使用前者,无论有目的的排序如何。例如,数组和列表可以作为 IReadOnlyList 返回,而 HashSet 最好作为 IReadOnlyCollection 返回。调用者总是可以根据需要将 I[ReadOnly]List 分配给 I[ReadOnly]Collection:此选择与公开的合约有关,而不是程序员 \xe2\x80\x9cclever\xe2\x80\x9d 或其他人将要执行的选择做。
\n看来你可以只返回一个合适的接口:
...
private readonly List<WorkItem> workItems = new List<WorkItem>();
// Usually, there's no need the property to be virtual
public virtual IReadOnlyList<WorkItem> WorkItems {
get {
return workItems;
}
}
...
Run Code Online (Sandbox Code Playgroud)
由于workItems字段实际上是List<T>如此自然的想法恕我直言是公开最宽的接口,这是IReadOnlyList<T>在这种情况下
| 归档时间: |
|
| 查看次数: |
31001 次 |
| 最近记录: |