如果我将IEnumerable<T>
作为类的属性公开,是否有可能由类的用户进行变异,如果是这样,什么是防止变异的最佳方法,同时保持已曝光属性的类型IEnumerable<T>
?
答案 0 :(得分: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 :(得分:2)
用户可能能够回送到集合类,因此公开。
collection.Select(x => x)
这将创建一个无法转换为集合的新IEnumerable
答案 2 :(得分:1)
该集合可以被转换回原始类型,如果它是可变的,那么它就可以被变异。
避免原始变异的可能性的一种方法是返回列表的副本。
答案 3 :(得分:1)
我不建议在迭代器中包装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()
返回的枚举器是一个结构,那么在任何情况下仍然必须装箱。
使用结构而不是类的性能优势通常相当轻微;但是,从概念上讲,使用结构符合一般建议,即属性不会创建新的堆对象实例。构造一个只包含对现有堆对象的引用的新结构实例非常便宜。使用这里定义的struct的最大缺点是它会锁定返回给调用代码的东西的行为,而简单地返回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);
}
}
convertor
委托可以是静态委托,因此不需要在运行时创建任何堆对象(在类初始化之外)。使用此方法,如果想要从List<int>
返回枚举器,则属性返回类型将为FancyWrappedEnumerable<int, List<int>.Enumerator, List>
。如果调用者直接在foreach
循环或var
声明中使用该属性,或者如果调用者想要以某种方式声明该类型的存储位置,那么可能是合理的使用var
。