2010-04-05 4 views
2

У меня есть класс, который имеет две коллекции HashSet<String> в качестве частных членов. Другие классы в моем коде хотели бы иметь возможность перебирать эти HashSets и читать их содержимое. Я не хочу писать стандартный getter, потому что другой класс все равно может сделать что-то вроде myClass.getHashSet().Clear(); Есть ли другой способ выставить элементы моих HashSets на итерацию, не подвергая ссылку на сам HashSet? Мне бы хотелось, чтобы я мог сделать это таким образом, который совместим с каждым циклом.(C#) итерация по частному члену частной коллекции только для чтения

+0

Спасибо всем, особ. Страгер за самый полный ответ, и Джон Скит за очистку моего ОП. – DGH

ответ

3

Выставьте IEnumerable<T> свойства:

public IEnumerable<whatevertype> MyHashSet { 
    get { 
     return this.myHashSet; 
    } 
} 

Конечно, пользователь этого кода может бросить эту IEnumerable<T> до HashSet<T> и редактирования элементов, так, чтобы быть на безопасной стороне (в то время как отрицательно скажется на производительность), вы можете делать:

public IEnumerable<whatevertype> MyHashSet { 
    get { 
     return this.myHashSet.ToArray(); 
    } 
} 

или:

public IEnumerable<whatevertype> MyHashSet { 
    get { 
     foreach(var item in this.myHashSet) { 
      yield return item; 
     } 
    } 
} 

Более производительный метод защиты, но менее удобно для абонента, чтобы возвращать IEnumerator<T>:

public IEnumerator<whatevertype> GetMyHashSetEnumerator() { 
    return this.myHashSet.GetEnumerator(); 
} 
+0

Зачем даже предлагать опцию 'ToArray'? Блок итератора более эффективен и не потребляет дополнительную память (кроме настройки для создаваемого класса-заглушки, созданного компилятором.) –

+0

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

+0

Интересный вопрос: работает ли 'return return' быстрее, чем' ToArray() '? –

3

Добавить метод/свойство так, чтобы не подвергать фактическую емкость:

public IEnumerable EnumerateFirst() 
{ 
    foreach(var item in hashSet) 
     yield return item; 
} 
+1

Лучше, так как это останавливает/действительно/отвратительных вызывающих лиц, бросающих IEnumerable обратно в HashSet. Однако вы не сильно набираете IEnumerable, что, возможно, не так хорошо. –

+1

+1. В то время как другие выступают за то, чтобы обнародовать участника только как «IEnumerable » (и это будет в строгом смысле, выполнить то, что вы хотите), что не является безопасной практикой. В то время как сам объект отображается только как интерфейс, нет ничего, что помешало бы предприимчивому разработчику вернуть объект обратно в «HashSet». Практика здесь безопаснее, поскольку вы никогда не раскрываете фактическую ссылку «HashSet». –

+0

... но это медленнее, грустно. – strager

-1

Марка ваш геттер выставляет HashSet как IEnumerable.

private HashSet<string> _mine; 

public IEnumerable<string> Yours 
{ 
    get { return _mine; } 
} 

Если общий тип изменчиво, то, что все еще может быть изменен, но никакие пункты не могут быть добавлены или удалены из HashSet.

+1

Да, они могут - вызывающий абонент просто должен вернуть обратно в 'HashSet '. –

+0

Хорошо, но тогда нет никакой гарантии безопасности в любом отношении. Даже если _mine является частным членом экземпляра, вы можете получить его с помощью отражения. Если потребитель решил нарушить контракт, предоставленный API, то они должны принять последствия этого. Выставляя его как «IEnumerable », вы делаете очевидным, что потребитель должен выполнять только итерацию и не изменять вашу коллекцию. Разве это недостаточно хорошо? –

+0

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

0

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

public IEnumerable<string> GetHashSetOneValues() 
{ 
    foreach (string value in hashSetOne) 
     yield return value; 
} 

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

foreach (string value in myObject.GetHashSetOneValues()) 
    DoSomething(value); 
3

Вы можете также использовать метод Select для создания оболочки, чем не может быть брошено назад HashSet<T>:

public IEnumerable<int> Values 
{ 
    get { return _values.Select(value => value); 
} 

Th избегает повторного использования по сравнению с _values дважды, как и с .ToArray(), но сохраняет реализацию на одной чистой линии.

+1

Великие мысли думают одинаково :) –

+0

Хмм, интересно ... – strager

6

Предполагая, что вы используете .NET 3.5, одной из альтернатив написанию кода yield является вызов метода LINQ. Например:

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

или

public IEnumerable<string> HashSet 
{ 
    get { return privateMember.Skip(0); } 
} 

Существуют различные операторы LINQ, которые могут быть использованы, как это - с помощью Skip(0), вероятно, является наиболее эффективным, так как после первоначального «пропустить значения 0» цикл, это вероятно, только цикл foreach/yield return показан в других ответах. Версия Select вызовет делегата проекции без операции для каждого полученного элемента.Шансы на то, что эта разница значительна, астрономически мала, однако - я предлагаю вам пойти с тем, что сделает код более ясным для вас.

+0

+1. Я бы пошел с «Пропустить», чтобы избежать лишних лямбда над головой. Хотя это не очень важно, на самом деле это ничего не значит. –

+0

+1; 'Skip (0)' отличная идея! – strager

0

Это может быть довольно поздно для вечеринки, но самым простым способом было бы использовать Linq. Вместо того, чтобы писать

public IEnumerable<string> GetValues() 
{ 
    foreach(var elem in list) 
     yield return elem; 
} 

вы можете написать

public IEnumerable<string> GetValues() => list;