c# - programacion - ¿Cómo debo usar las propiedades cuando se trata de miembros de la lista<T> de solo lectura?
propiedades en informatica definicion (7)
public List<int> li;
No declare campos públicos, generalmente se considera una mala práctica ... envuélvalo en una propiedad.
Puede exponer su colección como ReadOnlyCollection:
private List<int> li;
public ReadOnlyCollection<int> List
{
get { return li.AsReadOnly(); }
}
Cuando quiero hacer un tipo de valor de solo lectura fuera de mi clase, hago esto:
public class myClassInt
{
private int m_i;
public int i {
get { return m_i; }
}
public myClassInt(int i)
{
m_i = i;
}
}
¿Qué puedo hacer para hacer una List<T>
solo lectura (para que no puedan agregar / quitar elementos a / de ella) fuera de mi clase? Ahora solo lo declaro público:
public class myClassList
{
public List<int> li;
public myClassList()
{
li = new List<int>();
li.Add(1);
li.Add(2);
li.Add(3);
}
}
public class MyClassList
{
private List<int> _lst = new List<int>() { 1, 2, 3 };
public IEnumerable<int> ListEnumerator
{
get { return _lst.AsReadOnly(); }
}
}
Para verificarlo
MyClassList myClassList = new MyClassList();
var lst= (IList<int>)myClassList.ListEnumerator ;
lst.Add(4); //At this point ypu will get exception Collection is read-only.
public static IEnumerable<T> AsReallyReadOnly<T>(this IEnumerable<T> source)
{
foreach (T t in source) yield return t;
}
si agrego al ejemplo de 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);
Obtengo InvalidOperationException: Sequence contains no matching element
.
Existe un valor limitado al tratar de ocultar información en tal medida. El tipo de propiedad debe indicar a los usuarios qué pueden hacer con ella. Si un usuario decide que quiere abusar de su API, encontrará una manera. Bloquearlos del casting no los detiene:
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();
}
}
Con ese único método, podemos eludir las tres respuestas dadas en esta pregunta hasta el momento:
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
También:
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
Para reiterar ... ¡este tipo de "carrera de armamentos" puede continuar por el tiempo que quieras!
La única manera de detener esto es romper completamente el vínculo con la lista de fuentes, lo que significa que debe hacer una copia completa de la lista original. Esto es lo que hace el BCL cuando devuelve matrices. La desventaja de esto es que está imponiendo un costo potencialmente grande al 99.9% de sus usuarios cada vez que quieren acceso de solo lectura a algunos datos, porque le preocupa la piratería del 00.1% de los usuarios.
O simplemente podría negarse a admitir los usos de su API que eluden el sistema de tipo estático.
Si desea que una propiedad devuelva una lista de solo lectura con acceso aleatorio, devuelva algo que implemente:
public interface IReadOnlyList<T> : IEnumerable<T>
{
int Count { get; }
T this[int index] { get; }
}
Si (como es mucho más común) solo necesita ser enumerable secuencialmente, simplemente devuelva IEnumerable
:
public class MyClassList
{
private List<int> li = new List<int> { 1, 2, 3 };
public IEnumerable<int> MyList
{
get { return li; }
}
}
ACTUALIZACIÓN Desde que escribí esta respuesta, salió C # 4.0, por lo que la interfaz IReadOnlyList
anterior puede aprovechar la covarianza:
public interface IReadOnlyList<out T>
Y ahora .NET 4.5 ha llegado y tiene ... adivina qué ...
Por lo tanto, si desea crear una API autodocumentada con una propiedad que contenga una lista de solo lectura, la respuesta está en el marco.
Eric Lippert tiene una serie de artículos sobre Inmutabilidad en C # en su blog.
El primer artículo de la serie se puede encontrar aquí .
También puede encontrar útil la respuesta de Jon Skeet a una pregunta similar .
Puede exponerlo AsReadOnly . Es decir, devuelve un IList<T>
solo lectura. Por ejemplo ...
public ReadOnlyCollection<int> List
{
get { return _lst.AsReadOnly(); }
}
Solo devolver un IEnumerable<T>
no es suficiente. Por ejemplo ...
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(); }
}
}
La respuesta de Steve también tiene una forma ingeniosa de evitar el reparto.
La respuesta de JP con respecto a la devolución de IEnumerable<int>
es correcta (puede realizar un down-cast a una lista), pero esta es una técnica que impide el down-cast.
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(); }
}
}
La transformación de identidad durante la enumeración crea efectivamente un iterador generado por el compilador, un nuevo tipo que no está relacionado con _lst
de ninguna manera.