C # Как создать общедоступные методы получения и установки и частные методы для коллекции?

StackOverflow https://stackoverflow.com/questions/1080644

  •  22-08-2019
  •  | 
  •  

Вопрос

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

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

Обновить:Я хочу создать класс типа System.Data.SqlClient.SqlCommand.Для хранимых процедур вы можете использовать элемент "DeriveParameters", который заполняет коллекцию "Параметров", поэтому изменять можно только значение каждого элемента.

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

Это было полезно?

Решение

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

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

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

    int Count { get; }
}

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

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

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

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

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

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

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

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

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

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

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
}

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

Другие советы

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top