Существует ограниченная ценность попыток скрыть информацию в такой степени. Тип свойства должен сообщать пользователям, что им разрешено делать с ним. Если пользователь решит, что он хочет злоупотребить вашим 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, поэтому вышеуказанный интерфейс IReadOnlyList
может использовать ковариацию:
public interface IReadOnlyList<out T>
А теперь появился .NET 4.5 и он ... угадайте, что ...
Интерфейс IReadOnlyList
Так что, если вы хотите создать самодокументируемый API со свойством, которое содержит список только для чтения, ответ находится в структуре.