2011-02-04 29 views
9

如果我将IEnumerable<T>作为一个类的属性公开,它是否有可能被某个类的用户改变,如果是的话,是什么防止突变的最好方法,同时保留暴露的属性的类型IEnumerable<T>在属性中公开IEnumerable是否安全?

+0

使用Collections作为属性时有一些注意事项,请查看MSDN设计指南链接 https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/guidelines-for-collections – 2017-12-25 09:15:19

回答

14

这取决于你要返回的东西。如果你返回(比如说)一个可变的List<string>那么客户端确实可以将它重新设置为List<string>并对其进行变异。

你如何保护你的数据取决于你有什么开始。 ReadOnlyCollection<T>是一个很好的包装类,假设你有一个IList<T>开始。

如果您的客户端将无法从实施IList<T>ICollection<T>返回值中受益,你总是可以这样做:

public IEnumerable<string> Names 
{ 
    get { return names.Select(x => x); } 
} 

从而有效地包裹在集合中的迭代器。 (有各种不同的使用LINQ来隐藏源的方法...虽然没有记录哪些操作员隐藏源,哪些不隐藏源。例如调用Skip(0)确实将源隐藏在Microsoft实现中,但不是记录如此。)

Select绝对尽管隐藏来源。

+1

像往常一样,Jon Skeet提供了一个很好的答案。我只需要补充说,不同的数据收集类型并不意味着用于程序中的安全性。如果我们在完全信任的环境中运行,人们总是可以深入挖掘我们的对象,找到隐藏在内部的任何东西如果我们真的需要保护集合免受更改,我们需要使用应用程序安全机制。 – 2011-02-04 17:34:06

1

集合可以转换回原始类型,如果它是可变的,那么它可以被突变。

避免原始变异的可能性的一种方法是返回列表的副本

+0

做一个克隆是一种可能的方式,对,但这真的是“最好的方式”吗?也许有时候是这样,但有时候不是。 – 2011-02-04 17:29:14

+0

返回副本对于大型收藏是昂贵的。除非我真的需要复制的语义,否则我更喜欢JonSkeet建议的包装器。 – CodesInChaos 2011-02-04 17:29:55

2

用户可能能够转回集合类,因此公开。

collection.Select(x => x) 

,这将得到一个新的IEnumerable创建不能转换到集合

0

我不建议在一个迭代器包装一个IEnumerable防止收件人与基础连接胡闹。我的倾向是使用的包装是这样的:

public struct WrappedEnumerable<T> : IEnumerable<T> 
{ 
    IEnumerable<T> _dataSource; 
    public WrappedEnumerable(IEnumerable<T> dataSource) 
    { 
     _dataSource = dataSource; 
    } 
    public IEnumerator<T> GetEnumerator() 
    { 
     return _dataSource.GetEnumerator(); 
    } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return ((System.Collections.IEnumerable)_dataSource).GetEnumerator(); 
    } 
} 

如果属性的返回类型为IEnumerable<T>,从WrappedEnumerable<T>IEnumerable<T>类型压服框结构,使行为和性能匹配的一类。但是,如果属性被定义为返回类型WrappedEnumerable<T>,那么在调用代码将返回类型为WrappedEnumerable<T>的属性(很可能是由于类似于var myKeys = myCollection.Keys;的结果)返回时,可以保存装箱步骤)或直接在“foreach”循环中直接使用该属性。请注意,如果由GetEnumerator()返回的枚举器将是一个结构体,那么在任何情况下仍然必须装箱。

使用结构而不是类的性能优势通常会相当轻微;但从概念上讲,使用结构符合一般建议,即属性不会创建新的堆对象实例。构造一个只包含对现有堆对象引用的新结构实例非常便宜。使用此处定义的结构的最大缺点是它会锁定返回给调用代码的事件的行为,而仅仅返回IEnumerable<T>将允许其他方法。

还要注意,它可能在某些情况下是可以消除任何拳击比赛中的要求,并利用C#和vb.net foreach环鸭打字优化如果使用的类型,如:

public struct FancyWrappedEnumerable<TItems,TEnumerator,TDataSource> : IEnumerable<TItems> where TEnumerator : IEnumerator<TItems> 
{ 
    TDataSource _dataSource; 
    Func<TDataSource,TEnumerator> _convertor; 
    public FancyWrappedEnumerable(TDataSource dataSource, Func<TDataSource, TEnumerator> convertor) 
    { 
     _dataSource = dataSource; 
     _convertor = convertor; 
    } 
    public TEnumerator GetEnumerator() 
    { 
     return _convertor(_dataSource); 
    } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return _convertor(_dataSource); 
    } 
} 

代理可以是一个静态代理,因此不需要在运行时创建任何堆对象(在类初始化之外)。使用这种方法,如果有人想从List<int>返回一个枚举器,那么属性返回类型将是FancyWrappedEnumerable<int, List<int>.Enumerator, List>。如果调用者直接在foreach循环或var声明中直接使用该属性,也许是合理的,但如果调用方想要以不能使用var的方式声明该类型的存储位置,则应该是icky。

相关问题