2008-10-16 14 views
27

¿Es posible de alguna manera marcar un System.Array como inmutable. Cuando se ponen detrás de un público-get/set-privada que no se pueden añadir a, ya que requiere la re-asignación y reasignación, pero un consumidor aún puede establecer cualquier subíndice que desean:¿Son posibles matrices inmutables en .NET?

public class Immy 
{ 
    public string[] { get; private set; } 
} 

pensé la palabra clave readonly podría hacer el truco, pero no tuvo tanta suerte.

+4

Esa propiedad no se encuentra un nombre, ¿verdad? – Svish

+0

La propiedad se llama "inmutable"; es en este sentido que las cadenas son inmutables (a menos que acceda al puntero del carácter en código inseguro, lo que realmente no debería hacer porque están internados). –

+1

Tenga en cuenta que una matriz inmutable es tan inmutable como sus miembros. @PhilWhittington Quiso decir en el código, a la propiedad le falta un nombre. Es sintácticamente incorrecto. Sin embargo, no está destinado a ser un ejemplo de trabajo. – Zenexer

Respuesta

26

ReadOnlyCollection<T> es probablemente lo que está buscando. No tiene un método Add().

+1

Más exactamente, no tiene un método Add() expuesto públicamente. Tiene uno, porque las interfaces lo requieren. Pero un buen código de interfaz verificará .ReadOnly primero antes de llamarlo, porque hacerlo causaría una excepción. –

+2

Esto no es realmente lo mismo que una matriz inmutable: 'ReadOnlyCollection' no permite ninguna modificación, pero simplemente envuelve una' Lista' normal, que el creador aún puede cambiar. –

+3

@HenryJackson Al final, todos los datos en .NET se pueden modificar a través de Reflection. La inmutabilidad tan significativa es simplemente una cuestión de hacer que la mutabilidad sea inaccesible. Un contenedor de solo lectura a una colección mutable ciertamente cuenta como inmutable * si * la referencia a la colección mutable se descarta, haciendo que la mutación sea imposible. –

2

Creo que la mejor práctica es usar IList <T> en lugar de matrices en API públicas por este motivo. readonly evitará que una variable miembro se establezca fuera del constructor, pero como descubrió, no impedirá que las personas asignen elementos en la matriz.

Consulte Arrays Considered Somewhat Harmful para obtener más información.

Editar: Arrays no pueden ser leídos solamente, pero pueden ser convertidos a implementaciones IList vía Array.AsReadOnly de sólo lectura() como @shahkalpesh señala.

9

Puede usar el método Array.AsReadOnly para devolver.

+0

Solo para agregar una información: la clase 'ReadOnlyCollection' no tiene API como' Agregar', 'Agregar ', 'Eliminar' o' Eliminar ' para garantizar la naturaleza de solo lectura. Al mismo tiempo, sus indexadores también son de solo lectura. Si intentamos usar set indexer, se produce un error de tiempo de compilación: 'char [] myCharArray = {'\"', '<', '>', '|' }; var b1 = Array.AsReadOnly (myCharArray); b1 [0] = '\ 0'; // produce un error de tiempo de compilación. ' – RBT

28

El Framework Design Guidelines sugiere devolver una copia de la matriz. De esta forma, los consumidores no pueden cambiar los elementos de la matriz.

// bad code 
// could still do Path.InvalidPathChars[0] = 'A'; 
public sealed class Path { 
    public static readonly char[] InvalidPathChars = 
     { '\"', '<', '>', '|' }; 
} 

éstos son mejores:

public static ReadOnlyCollection<char> GetInvalidPathChars(){ 
    return Array.AsReadOnly(InvalidPathChars); 
} 

public static char[] GetInvalidPathChars(){ 
    return (char[])InvalidPathChars.Clone(); 
} 

Los ejemplos son directamente desde el libro.

+1

La segunda opción '(char []) InvalidPathChars.Clone();' Devuelve una copia, pero es modificable por el cliente, es decir, puede usar su copia para su propio propósito y la matriz original no se ve afectada. – RBT

0

Lo único que se debe agregar es que las matrices implican mutabilidad. Cuando devuelve un Array desde una función, está sugiriendo al programador cliente que pueden/deberían cambiar las cosas.

+0

¿Es esta una convención o existe una razón más específica para los clientes que esperan este comportamiento? – biozinc

0

Además de la respuesta de Matt, IList es una interfaz abstracta completa para una matriz, por lo que permite agregar, eliminar, etc. No estoy seguro de por qué Lippert parece sugerirlo como una alternativa a IEnumerable donde se necesita inmutabilidad. (Edit: porque la implementación IList puede arrojar excepciones para esos métodos de mutación, si te gusta ese tipo de cosas).

Quizás otra cosa a tener en cuenta es que los elementos en la lista también pueden tener estado mutable. Si realmente no desea que la persona que llama modifique dicho estado, tiene algunas opciones:

Asegúrese de que los elementos de la lista sean inmutables (como en el ejemplo: la cadena es inmutable).

Devuelve un clon profundo de todo, por lo que en ese caso podrías usar una matriz de todos modos.

Volver una interfaz que permite el acceso de sólo lectura a un artículo:

interface IImmutable 
{ 
    public string ValuableCustomerData { get; } 
} 

class Mutable, IImmutable 
{ 
    public string ValuableCustomerData { get; set; } 
} 

public class Immy 
{ 
    private List<Mutable> _mutableList = new List<Mutable>(); 

    public IEnumerable<IImmutable> ImmutableItems 
    { 
     get { return _mutableList.Cast<IMutable>(); } 
    } 
} 

Tenga en cuenta que todos los valores accesibles desde la interfaz IImmutable mismo debe ser inmutable (por ejemplo, cadena), o bien sea una copia que haga situ la mosca.

0

Lo mejor que puede hacer es extender una colección existente para crear la suya propia. El gran problema es que debería funcionar de manera diferente a cada tipo de colección existente porque cada llamada tendría que devolver una nueva colección.

0

Es posible que desee consultar my answer a una pregunta similar para obtener más ideas sobre la exposición de colecciones en un objeto.

1

.NET tiende a alejarse de las matrices para todos los casos menos el más simple y tradicional. Para todo lo demás, hay varias implementaciones enumerables/de colección.

Cuando desea marcar un conjunto de datos como inmutables, está yendo más allá de la capacidad proporcionada por una matriz tradicional. .NET proporciona una capacidad equivalente, pero no técnicamente en forma de matriz. Para tener una colección inmutable de una matriz, utilice Array.AsReadOnly<T>:

var mutable = new[] 
{ 
    'a', 'A', 
    'b', 'B', 
    'c', 'C', 
}; 

var immutable = Array.AsReadOnly(mutable); 

immutable habrá una instancia ReadOnlyCollection<char>. Como un caso de uso más general, puede crear un ReadOnlyCollection<T> desde cualquier implementación genérica de IList<T>.

var immutable = new ReadOnlyCollection<char>(new List<char>(mutable)); 

Tenga en cuenta que tiene que ser una implementación genérica; plain old IList no funcionará, lo que significa que no puede usar este método en una matriz tradicional, que solo implementa IList. Esto saca a la luz la posibilidad de usar Array.AsReadOnly<T> como un medio rápido para obtener acceso a implementaciones genéricas que normalmente son inaccesibles a través de una matriz tradicional.

ReadOnlyCollection<T> le dará acceso a todas las características que se esperan de una matriz inmutable:

// Note that .NET favors Count over Length; all but traditional arrays use Count: 
for (var i = 0; i < immutable.Count; i++) 
{ 
    // this[] { get } is present, as ReadOnlyCollection<T> implements IList<T>: 
    var element = immutable[i]; // Works 

    // this[] { set } has to be present, as it is required by IList<T>, but it 
    // will throw a NotSupportedException: 
    immutable[i] = element; // Exception! 
} 

// ReadOnlyCollection<T> implements IEnumerable<T>, of course: 
foreach (var character in immutable) 
{ 
} 

// LINQ works fine; idem 
var lowercase = 
    from c in immutable 
    where c >= 'a' && c <= 'z' 
    select c; 

// You can always evaluate IEnumerable<T> implementations to arrays with LINQ: 
var mutableCopy = immutable.ToArray(); 
// mutableCopy is: new[] { 'a', 'A', 'b', 'B', 'c', 'C' } 
var lowercaseArray = lowercase.ToArray(); 
// lowercaseArray is: new[] { 'a', 'b', 'c' } 
Cuestiones relacionadas