Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 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 в классе.