Безопасно ли выставлять IEnumerable в свойстве? - PullRequest
10 голосов
/ 04 февраля 2011

Если я выставлю IEnumerable<T> как свойство класса, есть ли вероятность того, что он может быть изменен пользователями класса, и если да, то каков наилучший способ защиты от мутации при сохранении незащищенного тип недвижимости IEnumerable<T>?

Ответы [ 4 ]

14 голосов
/ 04 февраля 2011

Это зависит от того, что вы возвращаете.Если вы вернете (скажем) изменяемый 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 определенно должен скрыть источник.

2 голосов
/ 04 февраля 2011

Пользователь может быть в состоянии привести обратно к классу коллекции, поэтому выставьте.

collection.Select(x => x)

и это создаст новый IEnumerable, который нельзя привести в коллекцию

1 голос
/ 14 сентября 2012

Я бы не советовал заключать 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);
    }
}

Делегат convertor может быть статическим делегатом и, следовательно, не требует создания объектов кучи во время выполнения (вне инициализации класса). Используя этот подход, если кто-то хочет вернуть перечислитель из List<int>, тип возвращаемого свойства будет FancyWrappedEnumerable<int, List<int>.Enumerator, List>. Возможно, разумно, если вызывающая сторона просто использовала свойство непосредственно в цикле foreach или в объявлении var, но довольно странно, если вызывающая сторона хотела объявить место хранения типа способом, который не может использовать var.

1 голос
/ 04 февраля 2011

Коллекция может быть возвращена к исходному типу, и если она является изменяемой, то она может быть затем видоизменена.

Один из способов избежать возможности изменения оригинала - это возврат копии списка.

...