Почему перечисления обработки по-другому к другим типам? Сохраните их в том же пространстве имен, поскольку они, вероятно, будут использоваться - и предположение, что они собираются быть используемыми другими классами, делают их типами верхнего уровня в их собственных файлах.
единственный тип типа, который я делаю обычно глыба вместе, является делегатами - у меня иногда есть файл Delegates.cs с набором делегатов в. Меньше с.NET 3.5 и Func/Action, заметьте.
Вы можете выставить его AsReadOnly . То есть вернуть доступную только для чтения оболочку IList
. Например ...
public ReadOnlyCollection<int> List
{
get { return _lst.AsReadOnly(); }
}
Простого возврата IEnumerable
недостаточно. Например ...
void Main()
{
var el = new ExposeList();
var lst = el.ListEnumerator;
var oops = (IList<int>)lst;
oops.Add( 4 ); // mutates list
var rol = el.ReadOnly;
var oops2 = (IList<int>)rol;
oops2.Add( 5 ); // raises exception
}
class ExposeList
{
private List<int> _lst = new List<int>() { 1, 2, 3 };
public IEnumerable<int> ListEnumerator
{
get { return _lst; }
}
public ReadOnlyCollection<int> ReadOnly
{
get { return _lst.AsReadOnly(); }
}
}
В ответе Стива также есть хитрый способ избежать каста.
Ответ JP относительно возврата IEnumerable
верен (вы можете преобразовать его в список), но вот метод, который предотвращает приведение вниз.
class ExposeList
{
private List<int> _lst = new List<int>() { 1, 2, 3 };
public IEnumerable<int> ListEnumerator
{
get { return _lst.Select(x => x); } // Identity transformation.
}
public ReadOnlyCollection<int> ReadOnly
{
get { return _lst.AsReadOnly(); }
}
}
Преобразование идентичности во время перечисления эффективно создает генерируемый компилятором итератор - новый тип, который никак не связан с _lst
.
Есть ограниченная ценность попыток скрыть информацию до такой степени. Тип свойства должен указывать пользователям, что им разрешено с ним делать. Если пользователь решит злоупотребить вашим API, он найдет способ. Блокирование их от кастинга не останавливает их:
public static class Circumventions
{
public static IList<T> AsWritable<T>(this IEnumerable<T> source)
{
return source.GetType()
.GetFields(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.Select(f => f.GetValue(source))
.OfType<IList<T>>()
.First();
}
}
С помощью этого единственного метода мы можем пока обойти три ответа, данные на этот вопрос:
List<int> a = new List<int> {1, 2, 3, 4, 5};
IList<int> b = a.AsReadOnly(); // block modification...
IList<int> c = b.AsWritable(); // ... but unblock it again
c.Add(6);
Debug.Assert(a.Count == 6); // we've modified the original
IEnumerable<int> d = a.Select(x => x); // okay, try this...
IList<int> e = d.AsWritable(); // no, can still get round it
e.Add(7);
Debug.Assert(a.Count == 7); // modified original again
Также:
public static class AlexeyR
{
public static IEnumerable<T> AsReallyReadOnly<T>(this IEnumerable<T> source)
{
foreach (T t in source) yield return t;
}
}
IEnumerable<int> f = a.AsReallyReadOnly(); // really?
IList<int> g = f.AsWritable(); // apparently not!
g.Add(8);
Debug.Assert(a.Count == 8); // modified original again
Повторяю ... это своего рода "гонка вооружений" "может продолжаться сколько угодно!
Единственный способ остановить это - полностью разорвать связь с исходным списком, что означает, что вам нужно сделать полную копию исходного списка. Это то, что делает BCL, когда возвращает массивы. Обратной стороной этого является то, что вы возлагаете потенциально большие затраты на 99,9% ваших пользователей каждый раз, когда они хотят получить доступ только для чтения к некоторым данным, потому что вы беспокоитесь о хакерских действиях 00,1% пользователей.
Или вы можете просто отказаться для поддержки использования вашего API в обход системы статических типов.
Если вы хотите, чтобы свойство возвращало список только для чтения с произвольным доступом, верните что-то, что реализует:
public interface IReadOnlyList<T> : IEnumerable<T>
{
int Count { get; }
T this[int index] { get; }
}
Если (что гораздо чаще) его нужно только перечислять последовательно, просто верните IEnumerable
:
public class MyClassList
{
private List<int> li = new List<int> { 1, 2, 3 };
public IEnumerable<int> MyList
{
get { return li; }
}
}
ОБНОВЛЕНИЕ Поскольку я написал этот ответ, вышел C # 4.0,
Эрик Липперт опубликовал в своем блоге серию статей о неизменяемости в C #.
Первую статью из этой серии можно найти здесь .
Вы также можете найти полезный ответ Джона Скита на аналогичный вопрос .
public static IEnumerable<T> AsReallyReadOnly<T>(this IEnumerable<T> source)
{
foreach (T t in source) yield return t;
}
, если я добавлю к примеру Earwicker
...
IEnumerable<int> f = a.AsReallyReadOnly();
IList<int> g = f.AsWritable(); // finally can't get around it
g.Add(8);
Debug.Assert(a.Count == 78);
, я получу InvalidOperationException: Sequence не содержит соответствующего элемента
.
public List<int> li;
Не объявляйте общедоступные поля, это обычно считается плохой практикой ... вместо этого оберните его в свойство.
Вы можете предоставить свою коллекцию как ReadOnlyCollection:
private List<int> li;
public ReadOnlyCollection<int> List
{
get { return li.AsReadOnly(); }
}
public class MyClassList
{
private List<int> _lst = new List<int>() { 1, 2, 3 };
public IEnumerable<int> ListEnumerator
{
get { return _lst.AsReadOnly(); }
}
}
Чтобы проверить
MyClassList myClassList = new MyClassList();
var lst= (IList<int>)myClassList.ListEnumerator ;
lst.Add(4); //At this point ypu will get exception Collection is read-only.