С# Как публиковать геттеры и сеттеры и частные методы для коллекции?

Я хотел бы иметь класс "A" с (например) свойством SortedList "SrtdLst", а внутри этого класса "A" допускают сложение или вычитание элементов "SrtdLst". Но в экземпляре класса "А" разрешено только получать или устанавливать содержимое элементов, а не добавлять новые элементы или вычитать существующие. В коде:

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: Я хочу создать класс, такой как System.Data.SqlClient.SqlCommand. Для хранимых процедур вы можете использовать член "DeriveParameters", который заполняет коллекцию "Параметры", поэтому можно изменять только значение каждого элемента.

Как это можно сделать?

Ответ 1

Если вы хотите запретить операции модификации во время компиляции, вам нужно безопасное решение.

Объявить интерфейс для общедоступных операций. Используйте этот интерфейс как тип свойства.

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

    int Count { get; }
}

Затем объявите класс, который реализует этот интерфейс и наследует от стандартного класса коллекции.

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

Предполагая, что вы правильно определяете определение интерфейса, вам не нужно ничего реализовывать вручную, поскольку базовый класс уже предоставляет реализации.

Используйте этот производный класс как тип поля, в котором хранится значение свойства.

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

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

В классе A вы можете напрямую использовать _list и, следовательно, изменять содержимое. Клиенты класса A смогут использовать только подмножество операций, доступных через IReadOnlyList<T>.

В вашем примере вы используете SortedList вместо List, поэтому, вероятно, интерфейс должен быть

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

Я также наследовал IEnumerable, который в любом случае является readonly, поэтому он абсолютно безопасен. Тогда безопасным классом будет:

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

Но в остальном это та же идея.

Обновление: только что заметили, что (по какой-то причине я не могу понять) вы не хотите запрещать модификацию операций - вы просто хотите запретить некоторые модифицирующие операции. Очень странно, но это все равно такое же решение. Независимо от того, какие операции вы хотите разрешить, "откройте их" в интерфейсе:

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

Конечно, что неправильное имя для интерфейса теперь... почему бы вам не хотеть запретить добавлять через Add, но не запрещать его с помощью индексатора? (Индексатор можно использовать для добавления элементов, как это может сделать метод Add.)

Обновление

Из вашего комментария, я думаю, вы имеете в виду, что хотите разрешить присвоение значению существующей пары ключ/значение, но запретить присвоение ранее неизвестному ключу. Очевидно, что, поскольку ключи указаны во время выполнения по строкам, нет способа поймать это во время компиляции. Таким образом, вы можете также проверить проверку выполнения:

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
}

В любое время, когда у вас есть обычный словарь, и вы хотите передать его некоторому методу, которому не доверяете, чтобы обновить существующие значения, оберните его одним из них.

Ответ 2

РЕДАКТИРОВАТЬ: оригинальный ответ ниже. Как заметил младший, я не заметил, что вы не просите об этом только для чтения - просто для предотвращения операции Add. Для меня это не похоже, так как разница между Add и установщиком индексатора заключается в том, что Add генерирует исключение, если элемент уже присутствует. В любом случае это может легко подделать вызывающий.

Почему вы хотите ограничить только одну операцию?


Оригинальный ответ

Во-первых, не используйте общедоступные поля. Это верный способ столкнуться с проблемами.

Похоже, вы хотите, чтобы класс оболочки только для чтения включал произвольный IDictionary. Затем вы можете иметь общедоступное свойство, которое возвращает оболочку, в то время как вы получаете доступ к частной переменной из вашего класса. Например:

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

Теперь вам нужно найти реализацию ReadOnlyDictionary... Я не могу ее реализовать прямо сейчас, но при необходимости я вернусь позже...

Ответ 3

Просто сделайте список приватным и выставьте его в качестве индексатора:

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

}

Теперь вы можете обращаться к элементам только с помощью индекса:

a["KeyA"] = "ValueBBB";

Однако, поскольку индексный список списка позволяет создавать новые элементы, вам нужно будет добавить код в индексаторе, чтобы предотвратить это, если вы не хотите, чтобы это было возможно.

Ответ 4

Если ключи известны за пределами класса, вы можете добавить в свой класс оболочки ChangeItem (key, newValue) и ReadItem (ключ). Затем сохраните класс SortedList в классе.