在属性中公开IEnumerable是否安全?

时间:2011-02-04 17:22:13

标签: c# .net ienumerable

如果我将IEnumerable<T>作为类的属性公开,是否有可能由类的用户进行变异,如果是这样,什么是防止变异的最佳方法,同时保持已曝光属性的类型IEnumerable<T>

4 个答案:

答案 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