2013-05-07 4 views
24

Например объявим:Как избежать открытого доступа к закрытым полям?

private readonly List<string> _strings = new List<string>(); 
public IEnumerable<string> Strings 
{ 
    get 
    { 
     return _strings; 
    } 
} 

И теперь мы можем сделать следующее:

((List<string>) obj.Strings).Add("Hacked"); 

Так что мы на самом деле не скрывая List от использования, но только скрыть его за интерфейс. Как скрыть список в таком примере без копирования _strings в новой коллекции, чтобы ограничить модификацию списка?

+0

Использование 'IEnumerable ' разумно здесь, если ваша (замечательная) цель - ограничить операции, которые могут выполнять пользователи. Если бы вы решили вернуть, скажем, массив вместо этого, вам не повезло; вы должны делать копию каждый раз. –

+0

@mattytommo: Нет доступа к аксессуарам. Для изменения коллекции вам не нужен набор средств доступа; вот в чем вопрос. –

+4

@mattytommo: Проблема не в том, что * Строки * будут меняться. Проблема в том, что * объект, возвращаемый Strings, сам является mutable *. –

ответ

35

Множество способов.

Самого наивный подход был бы всегда скопировать частное поле:

return _strings.ToList(); 

Но это вообще не нужно. Это бы имеет смысл, хотя, если:

  1. Вы случайно не знаете, что вызывающий код, как правило, хотят List<T> в любом случае, так что вы могли бы также дать один назад (не подвергая оригинал).
  2. Для выставленного объекта важно сохранить исходные значения, даже если исходная коллекция впоследствии будет изменена.

В качестве альтернативы, вы можете просто выставить только для чтения обертку:

return _strings.AsReadOnly(); 

Это более эффективно, чем первый вариант, но подвергнет изменения, внесенные в основной коллекции.

Эти стратегии также могут быть объединены. Если вам нужна копия оригинальной коллекции (поэтому изменения в оригинал не имеют никакого эффекта), но и не хотят, чтобы объект вернулся, чтобы быть изменяемым *, вы могли бы пойти с:

// copy AND wrap 
return _strings.ToList().AsReadOnly(); 

Вы можете также использовать yield :

foreach (string s in _strings) 
{ 
    yield return s; 
} 

Это похоже на эффективность и компромиссы по первому варианту, но с отсроченным исполнением.

Очевидно, что ключевое значение, которое следует учесть здесь, заключается в том, как ваш метод действительно будет использоваться и какую функциональность, именно, вы хотите обеспечить этим свойством.

* Хотя, честно говоря, я не уверен, почему вам все равно.

+4

+1 для использования 'yield return' –

+0

Или используйте метод расширения' AsEnumerable() '', а не 'yield return'. – thecoop

+0

'AsEnumerable()' не копирует значения. – Romoku

7

Как насчет использования ReadOnlyCollection. Это должно решить эту проблему.

private ReadOnlyCollection<string> foo; 
public IEnumerable<string> Foo { get { return foo; } } 
+0

OP не хочет, чтобы люди имели доступ к списку? «ReadOnlyCollection» по-прежнему предоставляет доступ для чтения к списку. – mattytommo

+2

Я уверен, что это была не проблема. Если вы не хотите публиковать что-либо публично, вы делаете его закрытым –

+0

Возможно, я ошибаюсь, надеюсь, OP может дать дополнительные разъяснения. – mattytommo

0

Помимо ReadOnlyCollection есть действительно Immutable Collections for .NET.

Использование неизменяемых коллекций гарантирует потребителю, что сбор не меняют, а не просто они не могут изменить их. Как они неизменны, вы можете просто выставить их:

public ImmutableList<string> Strings { get; private set; } 

Подробнее: http://blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx

3

вы можете обернуть свой список в поглотителе так:

private readonly List<string> _strings = new List<string>(); 
public IEnumerable<string> Strings { 
    get { 
    return new ReadOnlyCollection<string>(_strings); 
    } 
} 

Таким образом, вы можете свободно изменять ваш личный список внутри вашего класса, и не волнуйтесь об изменениях вне.

ПРИМЕЧАНИЯ: ReadOnlyCollection не копирует список он содержит (например, _strings)

+0

Не знаю, может быть, они думали, что ReadOnlyCollection копирует коллекцию? –

+0

@Tejas - это неверно, он остается в синхронизации с объектом –

+0

хмм да, вы на самом деле правы. Я полагаю, что он ссылается на фактическую коллекцию внутри страны. Круто. –

5

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

Как известно, инкапсуляция - очень полезная вещь, она помогает бороться с сложностью. Это делает ваш код логически минималистичным и понятным.

Как вы сказали, защищенный доступ к члену может быть нарушен, и пользователь интерфейса может получить полный доступ к базовому объекту. Но в чем проблема? Ваш код не станет более сложным или связанным.

На самом деле, насколько я знаю, с использованием какого-либо отражения почти нет ограничений, даже с полностью закрытыми членами. Так почему бы вам не попытаться скрыть их?

Я думаю, что нет необходимости делать копию коллекции (или любой другой изменчивой) и подходы выше неправильно. Используйте свой первоначальный вариант:

private readonly List<string> _strings = new List<string>(); 
public IEnumerable<string> Strings 
{ 
    get 
    { 
     return _strings; 
    } 
} 
+0

« И подходы выше неправильно »- это определенно не так, разные приложения имеют разные потребности. Если вы делаете банковское приложение, вы обязательно захотите принять меры, чтобы люди не могли просто произвольно писать в вашу коллекцию. –

+4

@Tejas У меня всегда есть теоретическая возможность изменить любой объект в моей памяти. Эссенциально, управляемый. Вот почему банковские приложения полагаются на онлайн-аутентификацию, проверку транзакций и т. Д., А не на конкретные типы коллекций в своих клиентских приложениях – astef

+0

, и тот факт, что вы можете перейти и изменить память, не имеет значения. Если вы пишете управляемый код, и вы хотели бы предоставить доступ к коллекции только для чтения, это кажется совершенно разумным делом. Согласитесь, я не согласен. –

1

Поскольку никто предложили это как ответ еще, вот еще одна альтернатива:

private readonly List<string> _strings = new List<string>(); 
public IEnumerable<string> Strings 
{ 
    get { return _strings.Select(x => x); } 
} 

Это эффективно эквивалентно yield return методу, описанному выше, но немного более компактным.

Смежные вопросы