Есть ограниченная ценность в попытках скрыть информацию до такой степени. Тип свойства должен указывать пользователям, что им разрешено с ним делать. Если пользователь решит злоупотребить вашим 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 со свойством, содержащим список только для чтения, ответ находится в структуре.
person
Daniel Earwicker
schedule
05.08.2009