2010-08-23 15 views
19

Gibt es eine Möglichkeit, eine schreibgeschützte Instanz eines Objekts zurückzugeben?C# readonly Objekt

public class Person 
{ 
    public String FirstName { get; set; } 
    public String LastName { get; set; } 
} 

public class SomeClass 
{ 
    public SomeClass(Person manager) 
    { 
     if (manager == null) 
      throw new ArgumentNullException("manager"); 

     _manager = manager; 
    } 

    private readonly Person _manager; 
    public Person Manager 
    { 
     get { return _manager; } //How do I make it readonly period! 
    } 
} 

ist der einzige Weg, dies zu tun durch einen Klon Rückkehr(), so dass Änderungen an der Klon und das Original nicht fertig sind? Ich weiß für Arrays gibt es eine Funktion, um das Array als schreibgeschützt zurückzugeben. Oh, und ich weiß, dass dies ein Referenztyp ist ... Ich frage mich außerdem, ob es eine versteckte C# -Funktion gibt, um den Schreibbereich zu sperren.

Ich habe versucht, mit einer Generic ReadOnly-Wrapper-Klasse zu kommen, konnte aber nicht herausfinden, wie man die Eigenschaften als readonly bekommt, ohne eine teure Reflexion und dergleichen zu machen.

Oh, und ich versuche wirklich zu vermeiden, eine zweite Version der Klasse, die alle Readonly-Eigenschaften ist, zu erstellen. An diesem Punkt könnte ich den Klon auch zurückgeben.

+1

Basierend auf den Kommentaren: Gibt es eine bekannte „Readonly“ generic-Wrapper-Klasse, die schnell und effizient? Derjenige, den ich zu machen versuchte, musste Reflexion verwenden, was ich nicht wirklich tun möchte, wenn ich es vermeiden kann. –

Antwort

33

Um sich vor dem Erstellen einer zusätzlichen Klasse zu schützen, können Sie es als Schnittstelle IPerson implementieren, die nur Leseeigenschaften hat.

public interface IPerson 
{ 
    string FirstName { get; } 
    string LastName { get; } 
} 
public class Person:IPerson 
{ 
    public String FirstName { get; set; } 
    public String LastName { get; set; } 
} 

public class SomeClass 
{ 
public SomeClass(Person manager) 
{ 
    if (manager == null) 
     throw new ArgumentNullException("manager"); 

    _manager = manager; 
} 

private readonly Person _manager; 
public IPerson Manager 
{ 
    get { return _manager; } //How do I make it readonly period! 
} 
} 
+1

+1, saubere Lösung für das Problem. –

+1

Es gibt sehr gute Gründe für die Verwendung von Schnittstellen und nicht von konkreten Typen (und wenn "Person" ein Verhalten hat, würde ich das auch empfehlen) ... aber wie spart man sich damit im Vergleich zum Erstellen einer Klasse? –

+5

Ich mag das auch, aber wäre es nicht einfach für sie, es einfach in eine Personenklasse zurück zu werfen? 'Person iCanChangeProperties = (Person) SomeClass.Manager;' Und die Besetzung schützt nicht davor, dass sie diese Referenz verwendet, um Änderungen an den Eigenschaften vorzunehmen ... Ich muss mich vielleicht einfach an den Klon halten. Die ReadOnly-Schnittstelle wäre zwar nett, aber die Reflektion sagt ihnen, dass sie KEINESFALLS Setter-Eigenschaften haben können. –

2

Es gibt keine solche Funktion - Sie haben Ihre Optionen abgedeckt.

Entweder klonen Sie es oder machen Sie einen schreibgeschützten Person Typ. Der letztere Ansatz wird normalerweise bevorzugt, weil die Semantik klarer ist: Für Anrufer ist es offensichtlich, dass sie die Instanz nicht modifizieren sollen (und können).

+0

Dieser Ansatz ist auch in der C# -Standardbibliothek ziemlich üblich. Sehen Sie Dinge wie 'String' /' StringBuilder' und 'Uri' /' UriBuilder'. Es macht nur Sinn, wenn die Read-Only-Verwendung viel zahlreicher ist, was bei diesen beiden der Fall ist. – Ekevoo

0

Nein. Sie suchen nach etwas wie C++ - Stil const -ness, und für variousreasons C# hat das nicht.

Anonyme Typen sind jedoch wirklich unveränderlich.

1

Es gibt keine Möglichkeit, alle Eigenschaften Ihres Objekts schreibgeschützt extern von der Klasse zu machen. In Ihrem obigen Beispiel können Sie die _manager-Eigenschaften nicht schreibgeschützt machen, es sei denn, Sie haben die Eigenschaften in der Person-Klasse so geändert, dass sie nur gelesen werden.

Sie können den Setter der Eigenschaften der Person-Klasse intern setzen, was bedeutet, dass nur Klassen innerhalb derselben Baugruppe wie Person die Eigenschaften ändern können.

Oder, wenn Sie den Setter der Eigenschaften privat machen, dann kann nur Code in Person die Werte der Eigenschaften ändern.

4

Sie können Objekt einfrieren (es unveränderlich machen) unter bestimmten Bedingungen mit Hilfe von Castle.DynamicProxy. Lesen Sie diesen Blog post für Details.

public class Person 
{ 
    public Person(string firstName, string lastName) 
    { 
     FirstName = firstName; 
     LastName = lastName; 
    } 

    public String FirstName { get; private set; } 
    public String LastName { get; private set; } 

} 
5

Sie können die Klasse Person in ein unveränderliches Objekt, wie weiter unten .. verwandeln.

0

Anonyme Objekte sind schreibgeschützt

1

Hier ein weiteres Beispiel, basierend auf der Implementierung von List.AsReadOnly in .NET Framework 2.0.Ein boolean (IsReadOnly) wird in geeigneten Methoden verwendet Updates zu verhindern:

public class Person 
{ 
    private string _firstName; 
    public string FirstName 
    { 
     get { return _firstName; } 
     set 
     { 
      if (!IsReadOnly) _firstName = value; 
      else throw new AccessViolationException("Object is read-only."); 
     } 
    } 

    private string _lastName; 
    public string LastName 
    { 
     get { return _lastName; } 
     set 
     { 
      if (!IsReadOnly) _lastName = value; 
      else throw new AccessViolationException("Object is read-only."); 
     } 
    } 

    internal virtual bool IsReadOnly { get { return false; } } 

    public ReadOnlyPerson AsReadOnly() 
    { 
     return new ReadOnlyPerson(this); 
    } 

    public class ReadOnlyPerson : Person 
    { 
     private Person _person; 
     internal override bool IsReadOnly { get { return true; } } 

     internal ReadOnlyPerson(Person person) // Contructor 
     { 
      this._person = person; 
     } 
    } 
} 

es zu testen:

static void Main(string[] args) 
{ 
    Person p1 = new Person(); 
    p1.FirstName = "Joe"; 
    p1.LastName = "Bloe"; 
    Console.WriteLine("First = {0} Last = {1}", p.FirstName, p.LastName); 

    var p2 = p1.AsReadOnly(); 
    p2.FirstName = "Josephine"; // AccessViolationException 
}