如何在C#中为集合创建公共的getter和setter以及私有方法?

3

我希望创建一个名为"A"的类,其中包含一个名为"SrtdLst"的SortedList集合属性。在该类内部,允许添加或删除"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”填充“Parameters”集合,因此只能修改每个项的值。
如何做到这一点?

5
请告诉我你不会天天使用禁用元音符号表示法!我猜这只是为了举例而已。我整天在工作中处理符号,我称之为“允许一个元音字母,但它并不总是第一个!”;这很难阅读。 - jcollum
1
是的,同意,但这是一个周五晚上的帖子... - Alex
4个回答

7

如果你想在编译时禁止修改操作,你需要一个类型安全的解决方案。

声明一个公共允许操作的接口。将该接口用作属性类型。

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,这个接口是只读的,所以非常安全。安全类如下:

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”方法添加。正如我在之前对Skeet的评论中所说的那样,我想创建一个类似于System.Data.SqlClient.SqlCommand的存储过程类,其中包含一个名为“DeriveParameters”的成员,该成员填充“Parameters”集合,因此只能修改每个项的值。 - Alex
FYI,.NET 4.5 版本确切地拥有这些接口:IReadOnlyList<T> 和 IReadOnlyDictionary<TKey, TValue>。 - Mike Chaliy
@MikeChaliy - 是的,我很高兴在VS11预览版中看到了它们! - Daniel Earwicker

6

编辑:原始答案如下。正如earwicker指出的那样,我没有注意到你并不是要求它为只读 - 只是要防止 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的实现...我现在无法实现它,但如果需要的话,稍后我会回来...


该死,你和你的无所不在的 skeet :P - annakata
我不喜欢在运行时检测错误,因此我的答案使用静态类型系统。但另一个问题(一开始我也没有注意到!)是OP并没有要求它是只读的。他们只是想禁止Add方法,但仍允许通过索引器进行赋值(这也可以添加项目)。非常奇怪... - Daniel Earwicker
哦 - 我没有注意到它在添加方面必须是只读的。不好。我会进行编辑。 - Jon Skeet
我想创建一个类似于System.Data.SqlClient.SqlCommand的类,用于存储过程,并带有一个名为“DeriveParameters”的成员和一个半只读的“Parameters”属性。并使用它来打印、显示和保存SQL Server报表。你还觉得这很奇怪吗? - Alex
你知道 ReadOnlyDictionaryWrapper 的命名空间吗?因为我找不到它... - Alex
@Alex:在框架中没有这种方法-您必须编写自己的实现。 - Jon Skeet

2

只需将列表设为私有,并将其公开为索引器:

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

然而,由于列表的索引器允许创建新项目,如果您不希望这种情况发生,您需要在索引器中添加代码来防止这种情况发生。


0
如果类外部已知键,则可以在包装器类中添加ChangeItem(key,newValue)和ReadItem(key)方法。然后将SortedList保持为类的私有属性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接