2008-10-16 12 views
27

Ist es möglich, ein System.Array irgendwie unveränderlich zu markieren. Wenn hinter einem setzen öffentlich-get/Private-Set können sie nicht hinzugefügt werden, da es Neuvergabe und Neuzuweisung erfordert, sondern ein Verbraucher kann so eingestellt haupt noch tief sie wollen:Sind unveränderliche Arrays in .NET möglich?

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

Ich dachte, das readonly Schlüsselwort könnte den Trick, aber kein solches Glück tun.

+4

Diese Eigenschaft einen Namen fehlen, ist es nicht? – Svish

+0

Die Eigenschaft heißt "unveränderlich" - in diesem Sinne sind Strings unveränderlich (außer Sie greifen auf den Char-Zeiger in unsicheren Code zu, den Sie wirklich nicht tun sollten, weil sie interniert sind.) –

+1

Denken Sie daran, dass ein unveränderliches Array ist nur so unveränderlich wie seine Mitglieder. @PhilWhittinton Er meinte im Code, der Eigenschaft fehlt ein Name. Es ist syntaktisch falsch. Es ist jedoch nicht wirklich ein funktionierendes Beispiel. – Zenexer

Antwort

26

ReadOnlyCollection<T> ist wahrscheinlich das, was Sie suchen. Es hat keine Add() Methode.

+1

Genauer gesagt, es hat keine öffentlich zugängliche Add() - Methode. Es hat einen, weil die Schnittstellen es erfordern. Aber guter Interface-Code prüft .ReadOnly zuerst, bevor er aufgerufen wird, weil dies eine Ausnahme verursachen würde. –

+2

Das ist nicht dasselbe wie ein unveränderbares Array - 'ReadOnlyCollection' lässt keine Änderungen zu, aber es wird nur eine reguläre' List' umbrochen, die vom Ersteller noch geändert werden kann. –

+3

@HenryJackson Am Ende sind alle Daten in .NET über Reflection änderbar. So bedeutungsvolle Unveränderlichkeit ist einfach eine Frage der Unzugänglichkeit der Wandlungsfähigkeit. Ein schreibgeschützter Wrapper für eine veränderbare Sammlung gilt als unveränderlich *, wenn der Verweis auf die veränderbare Sammlung verworfen wird, was eine Mutation unmöglich macht. –

2

Ich glaube, Best Practice ist aus diesem Grund IList <T> anstatt Arrays in öffentlichen APIs zu verwenden. readonly verhindert, dass eine Membervariable außerhalb des Konstruktors gesetzt wird, verhindert jedoch, wie Sie festgestellt haben, nicht, dass Personen Elemente im Array zuweisen.

Weitere Informationen finden Sie unter Arrays Considered Somewhat Harmful.

Bearbeiten: Arrays können nicht nur gelesen werden, sondern können über Array.AsReadOnly() in schreibgeschützte IList-Implementierungen konvertiert werden, wie @shahkalpesh angibt.

9

Sie können die Methode Array.AsReadOnly verwenden, um zurückzukehren.

+0

Nur um eine Information hinzuzufügen - 'ReadOnlyCollection' Klasse hat keine API wie' Add', 'Add ', 'Remove' oder' Remove 'um die schreibgeschützte Natur zu gewährleisten. Gleichzeitig sind ihre Indexer auch schreibgeschützt. Wenn wir versuchen, set indexer zu verwenden, führt dies zu einem Kompilierzeitfehler - 'char [] myCharArray = {'\' ',' <', '> ',' | ' }; var b1 = Array.AsReadOnly (myCharArray); b1 [0] = '\ 0'; // führt zur Kompilierzeit error.' – RBT

28

Die Framework Design Guidelines schlägt vor, eine Kopie des Arrays zurückzugeben. Auf diese Weise können Verbraucher keine Artikel aus dem Array ändern.

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

diese sind besser:

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

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

Die Beispiele direkt aus dem Buch sind.

+1

Die zweite Option '(char []) InvalidPathChars.Clone();' Gibt eine Kopie zurück, aber sie kann vom Client geändert werden, dh er kann seine Kopie für seinen eigenen Zweck verwenden und das ursprüngliche Array bleibt davon unberührt – RBT

0

Das einzige, was hinzuzufügen ist, dass Arrays implizieren Wandlungsfähigkeit. Wenn Sie ein Array von einer Funktion zurückgeben, schlagen Sie dem Client-Programmierer vor, dass sie Dinge ändern können/sollten.

+0

Ist dies eine Konvention oder gibt es einen spezifischeren Grund für Kunden, die dieses Verhalten erwarten? – biozinc

0

Neben Matts Antwort ist IList eine vollständige abstrakte Schnittstelle zu einem Array, so dass es hinzufügen, entfernen, etc. Ich bin nicht sicher, warum Lippert scheint es als Alternative zu IEnumerable vorschlagen, wo Unveränderlichkeit benötigt wird. (Edit:, weil die IList-Implementierung Ausnahmen für diese Mutationsmethoden auslösen kann, wenn Sie so etwas mögen).

Vielleicht eine andere Sache zu beachten, dass die Elemente auf der Liste auch veränderbaren Zustand haben können. Wenn Sie nicht möchten, dass der Aufrufer diesen Zustand ändert, haben Sie einige Optionen:

Stellen Sie sicher, dass die Elemente in der Liste unveränderlich sind (wie in Ihrem Beispiel: Zeichenfolge ist unveränderlich).

Geben Sie einen tiefen Klon von allem zurück, also könnten Sie in diesem Fall trotzdem ein Array verwenden.

Return eine Schnittstelle, die Nur-Lese-Zugriff auf ein Element gibt:

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>(); } 
    } 
} 

Beachten Sie, dass jeder Wert erreichbar von der IImmutable Schnittstelle muss selbst unveränderlich (zB String) oder auch eine Kopie sein, die Sie machen on- die Fliege.

0

Das Beste, was Sie tun können, ist eine bestehende Sammlung zu erweitern, um Ihre eigenen zu erstellen. Das große Problem ist, dass es anders funktionieren müsste als jeder vorhandene Sammlungstyp, weil jeder Aufruf eine neue Sammlung zurückgeben müsste.

0

Vielleicht möchten Sie my answer zu einer ähnlichen Frage für weitere Ideen zum Freilegen von Sammlungen auf einem Objekt.

1

.NET neigt dazu, von Arrays für alle außer den einfachsten und traditionellsten Anwendungsfällen abzulenken. Für alles andere gibt es verschiedene Aufzählungs-/Sammlungsimplementierungen.

Wenn Sie eine Gruppe von Daten als unveränderbar markieren möchten, gehen Sie über die von einem herkömmlichen Array bereitgestellten Funktionen hinaus. .NET bietet die gleiche Funktionalität, jedoch nicht technisch in Form eines Arrays. Um eine unveränderliche Sammlung aus einem Array zu erhalten, verwenden Array.AsReadOnly<T>:

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

var immutable = Array.AsReadOnly(mutable); 

immutable wird eine ReadOnlyCollection<char> Instanz sein. Als allgemeinerer Anwendungsfall können Sie eine ReadOnlyCollection<T> aus einer generischen IList<T> Implementierung erstellen.

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

Beachten Sie, dass es sich um eine generische Implementierung handelt; plain old IList wird nicht funktionieren, was bedeutet, dass Sie diese Methode nicht für ein traditionelles Array verwenden können, das nur IList implementiert. Dies zeigt die Möglichkeit, Array.AsReadOnly<T> als eine schnelle Möglichkeit zu verwenden, um Zugriff auf generische Implementierungen zu erhalten, die normalerweise nicht über ein herkömmliches Array zugänglich sind.

ReadOnlyCollection<T> geben Sie Zugriff auf alle Funktionen, die Sie von einem unveränderlichen Array erwarten:

// 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' } 
Verwandte Themen