Domanda

Mi piacerebbe avere una classe "A" con un (per esempio) la raccolta SortedList "SrtdLst" proprietà, e all'interno di questa classe "A" consentire l'aggiunta o la sottrazione di voci "SrtdLst". Ma in un'istanza della classe "A", consentire solo per ottenere o impostare il contenuto delle voci, non per aggiungere nuovi elementi o sottrarre quelli esistenti. In codice:

class A
{
    public SortedList<string, string> SrtdLst = new SortedList<string, string>();

    public A()
    {
        // This must work:
        SrtdLst.Add("KeyA", "ValueA");
        // This too:
        SrtdLst["KeyA"] = "ValueAAA";
    }
}

class B
{
    public A a = new A();
    public B()
    {
        // I want the following code to fail:
        a.SrtdLst.Add("KeyB", "ValueB");
        // But this must work:
        a.SrtdLst["KeyA"] = "ValueBBB";
   }
}

UPDATE: Voglio creare una classe come System.Data.SqlClient.SqlCommand. Per le stored procedure è possibile utilizzare gli stati "DeriveParameters" che riempie una collezione di "Parametri", in modo che solo il valore di ogni elemento può essere modificato.

Come si può fare?

È stato utile?

Soluzione

Se si vuole mettere al bando le operazioni di modifica in fase di compilazione, è necessaria una soluzione type-safe.

Dichiarare un'interfaccia per le operazioni consentite pubblicamente. Utilizzare tale interfaccia come il tipo di proprietà.

public interface IReadOnlyList<T>
{
    T this[int index] { get; }

    int Count { get; }
}

Quindi dichiarare una classe che implementa l'interfaccia ed eredita dalla classe collezione standard.

public class SafeList<T> : List<T>, IReadOnlyList<T> { }

Supponendo che si ottiene la giusta definizione di interfaccia, non sarà necessario implementare nulla a mano, come classe base fornisce già le implementazioni.

L'uso che deriva classe come il tipo di campo che memorizza il valore della proprietà.

public class A
{
    private SafeList<string> _list = new SafeList<string>();

    public IReadOnlyList<string>
    {
        get { return _list; }
    }
}

In classe A, è possibile utilizzare _list direttamente, e quindi modificare il contenuto. I clienti di classe A saranno in grado di utilizzare il sottoinsieme di operazioni disponibili tramite IReadOnlyList<T>.

Per il tuo esempio, si sta utilizzando SortedList al posto di lista, quindi l'interfaccia probabilmente ha bisogno di essere

public interface IReadOnlyDictionary<K, V> : IEnumerable<KeyValuePair<K, V>>
{
    V this[K index] { get; }        
}

Ho fatto che eredita IEnumerable pure, che è di sola lettura comunque, quindi è perfettamente sicuro. La classe di sicuro sarebbe allora:

public class SafeSortedList<K, V> : SortedList<K, V>, IReadOnlyDictionary<K, V> { }

Ma per il resto è la stessa idea.

Update: appena notato che (per qualche motivo non riesco a capire) non si vuole mettere al bando le operazioni di modifica - si vuole solo mettere al bando alcune operazioni che modificano. Molto strano, ma è ancora la stessa soluzione. Qualunque sia operazioni che si desidera consentire, "aprirli" nell'interfaccia:

public interface IReadOnlyDictionary<K, V> : IEnumerable<KeyValuePair<K, V>>
{
    V this[K index] { get; set; }        
}

Naturalmente, questo è il nome sbagliato per l'interfaccia ora ... perché sulla terra si vuole vietare l'aggiunta tramite add ma non vietare tramite l'indicizzatore? (L'indicizzatore può essere utilizzato per aggiungere elementi, proprio come il metodo Add lattina.)

Aggiorna

Dal tuo commento Credo che si intende che si desidera consentire l'assegnazione al valore di una coppia chiave / valore esistente, ma non consentire l'assegnazione di una chiave precedentemente sconosciuto. Ovviamente, come le chiavi sono specificate in fase di esecuzione da stringhe, non c'è modo di catturare che al momento della compilazione. Così si può anche andare per il controllo runtime:

public class FixedSizeDictionaryWrapper<TKey, TValue> : IDictionary<TKey, TValue>
{
    IDictionary<TKey, TValue> _realDictionary;

    public FixedSizeDictionaryWrapper(IDictionary<TKey, TValue> realDictionary)
    {
        _realDictionary = realDictionary;
    }

    public TValue this[TKey key]
    {
        get { return _realDictionary[key]; }

        set 
        {
            if (!_realDictionary.Contains(key))
                throw new InvalidOperationException();

            _realDictionary[key] = value;
        }
    }

    // Implement Add so it always throws InvalidOperationException

    // implement all other dictionary methods to forward onto _realDictionary
}

Ogni volta che si dispone di un dizionario ordinario e si desidera a portata di mano a qualche metodo che non ti fidi di aggiornare i valori esistenti, avvolgerla in una di queste.

Altri suggerimenti

EDIT: risposta originale è al di sotto. Come sottolinea Earwicker, non avevo notato che non sono per chiedere che sia in sola lettura - solo per evitare che l'operazione Add. Che non suona come una buona idea per me, come l'unica differenza tra Add e l'indicizzatore-setter è che Add genera un'eccezione se l'elemento è già presente. Che potrebbe facilmente essere falsificate dal chiamante in ogni caso.

Perché si vuole limitare solo che un'operazione?


risposta originale

Per prima cosa, non utilizzare campi pubblici. Questo è un modo sicuro per incorrere in problemi.

Sembra che si vuole una classe wrapper di sola lettura intorno ad un IDictionary arbitraria. È quindi possibile avere una proprietà pubblica che restituisce l'involucro, mentre si accede alla variabile privata da dentro la classe. Ad esempio:

class A
{
    private SortedList<string, string> sortedList = new SortedList<string, string>();

    public IDictionary<string, string> SortedList 
    {
        get { return new ReadOnlyDictionaryWrapper(sortedList);
    }

    public A()
    {
        sortedList.Add("KeyA", "ValueA");
        sortedList["KeyA"] = "ValueAAA";
    }
}

Ora che hai appena avuto modo di trovare un'implementazione ReadOnlyDictionary ... non può implementare in questo momento, ma sarò di nuovo in seguito, se necessario, ...

Basta fare la lista privata, ed esporlo come un indicizzatore:

class A {

   private SortedList<string, string> _list;

   public A() {
      _list = new SortedList<string, string>()
   }

   public string this[string key] {
      get {
         return _list[key];
      }
      set {
         _list[key] = value;
      }
   }

}

Ora è possibile accedere solo gli oggetti utilizzando l'indice:

a["KeyA"] = "ValueBBB";

Tuttavia, come l'indicizzatore della lista permette la creazione di nuovi oggetti, si dovrà aggiungere il codice nel indicizzatore per evitare che, se non si vuole che lo fanno essere possibile.

Se i tasti sono conosciuti al di fuori della classe, allora si può aggiungere un ChangeItem (chiave, newValue) e ReadItem (tasto) per la classe wrapper. Quindi tenere il SortedList privata alla classe.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top