2010-08-23 14 views
19

Esiste un modo per restituire un'istanza di un oggetto readonly?C# readonly object

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! 
    } 
} 

è l'unico modo per farlo restituendo un clone() in modo che tutte le modifiche sono fatte per il Clone e non l'originale? So per gli array c'è una funzione per restituire l'array come di sola lettura. Oh, e so che questo è un tipo di riferimento ... Inoltre mi chiedo se ci sia qualche caratteristica nascosta in C# per bloccare la porzione di scrittura.

Stavo cercando di creare una classe di wrapper ReadOnly generica, ma non riuscivo a capire come ottenere le proprietà come readonly senza fare un po 'di riflessione costosa e così via.

Oh, e sto davvero cercando di evitare di creare una seconda versione della classe che sia tutte proprietà di sola lettura. A quel punto, potrei anche restituire il clone.

+1

Sulla base delle osservazioni: c'è qualche nota "ReadOnly" classe wrapper generico che è veloce e efficiente? Quello che ho provato a fare ha dovuto usare la riflessione, che non è proprio quello che voglio fare se posso evitarlo. –

risposta

33

Per evitare di creare una classe extra, è possibile renderla implementata come interfaccia IPerson che ha solo proprietà di sola lettura.

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, soluzione pulita al problema. –

+1

Ci sono ottimi motivi per usare le interfacce piuttosto che i tipi concreti (e se "Person" ha qualche comportamento, lo raccomanderei anch'io) ... ma come si risparmia qualsiasi sforzo rispetto alla creazione di una classe? –

+5

Anche a me piace questo, ma non sarebbe facile per loro riportarlo di nuovo in una classe Person? 'Persona iCanChangeProperties = (Persona) SomeClass.Manager;' E il Cast non protegge contro di loro usando quel riferimento per apportare modifiche alle proprietà ... Potrei semplicemente limitarmi a clonare. Tuttavia, l'interfaccia di ReadOnly sarebbe carina, quindi la riflessione dice loro che ** NON POSSONO ** fare le proprietà di setter. –

2

Non esiste una funzionalità di questo tipo: hai coperto le opzioni.

O clonarlo o creare un tipo di lettura solo Person. Il secondo approccio è solitamente preferito perché la semantica è più chiara: è ovvio per i chiamanti che non dovrebbero (e non possono) modificare l'istanza.

+0

Questo approccio è abbastanza comune anche nella libreria standard C#. Vedi cose come 'String' /' StringBuilder' e 'Uri' /' UriBuilder'. Ha senso solo quando l'uso di sola lettura è molto più ampio, come nel caso di questi due. – Ekevoo

0

No. Stai cercando qualcosa come C++ - stile const -ness, e per variousreasons C# non ha quello.

Tuttavia, i tipi anonimi sono veramente immutabili.

1

Non c'è un modo per rendere esternamente tutte le proprietà dell'oggetto direttamente dalla classe. Nell'esempio sopra riportato, non è possibile rendere le proprietà _manager in sola lettura, a meno che non siano state modificate le proprietà all'interno della classe Person per essere di sola lettura.

È possibile rendere interno il setter delle proprietà della classe Person, vale a dire che solo le classi all'interno dello stesso assieme di Person possono modificare le proprietà.

Oppure, se si rende privato il settatore delle proprietà, solo il codice all'interno di Persona può modificare i valori delle proprietà.

4

È possibile congelare oggetto (renderlo immutabile) in alcune condizioni con l'aiuto di Castle.DynamicProxy. Leggi questo blog post per i dettagli.

5

È possibile trasformare la classe Person in un oggetto immutabile, come seguito ..

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

} 
0

oggetti anonimi sono di sola lettura.

1

Ecco un altro esempio, basato su come List.AsReadOnly è implementato in .NET Framework 2.0.Un booleano (IsReadOnly) è usato in metodi appropriati per impedire gli aggiornamenti:

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

per testarlo:

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 
}