【问题标题】:C# How to make public getters and setters and private methods for a collection?C#如何为集合创建公共getter和setter以及私有方法?
【发布时间】:2009-07-03 19:56:00
【问题描述】:

我想要一个具有(例如)SortedList 集合“SrtdLst”属性的类“A”,并且在这个类“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”,因此只能修改每个项目的值。

如何做到这一点?

【问题讨论】:

  • 请告诉我你每天都不要使用不允许元音的符号!我想这只是为了这个例子。我整天在工作中处理我称之为“允许一个元音,但并不总是第一个!”的符号;读起来很糟糕。
  • 是的,同意,但这是周五晚上的帖子...

标签: c# collections setter getter


【解决方案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&lt;T&gt; 提供的操作子集。

对于您的示例,您使用的是 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
}

任何时候你有一个普通的字典,并且你想把它交给一些你不信任的方法来更新现有值,把它包装在其中一个中。

【讨论】:

  • 我想禁止所有,但我想禁止索引器通过设置器添加项目,但我不知道如何通过“添加”方法禁止添加。正如我在之前对 Skeet 的评论中所说,我想要为存储过程创建一个像 System.Data.SqlClient.SqlCommand 这样的类,其中有一个像“DeriveParameters”这样的成员来填充“参数”的集合,所以只有每个项目的值都可以修改。
  • 仅供参考 .NET 4.5 正好有这个接口 IReadOnlyList 和 IReadOnlyDictionary
  • @MikeChaliy - 是的,很高兴看到 VS11 预览版中的这些人!
【解决方案2】:

编辑:原始答案如下。正如earwicker 指出的那样,我没有注意到您没有要求它是只读的 - 只是为了防止Add 操作。这对我来说听起来不是一个好主意,因为 Add 和 indexer-setter 之间的唯一区别是 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 实现...我现在无法实现它,但如果有必要我会稍后回来...

【讨论】:

  • 该死的你和你的无所不在的飞碟:P
  • 我不喜欢在运行时检测错误,因此我的答案使用静态类型系统。但另一个问题(我一开始也没有注意到这一点!)是 OP 并没有要求它是只读的。他们只想禁止 Add 方法,但仍然允许通过索引器分配(也可以添加项目)。很奇怪……
  • 哦 - 我没有注意到它在 Add 方面必须是只读的。不好。将编辑。
  • 我想为存储过程创建一个像 System.Data.SqlClient.SqlCommand 这样的类,其中的成员像“DeriveParameters”是一个半只读的“Parameters”属性。并使用它来打印、显示和保存 SQL Server 报告。还觉得很奇怪??
  • 你知道 ReadOnlyDictionaryWrapper 的命名空间吗,因为我找不到...
【解决方案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(key) 添加到您的包装类。然后将 SortedList 保持为类的私有。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-27
      • 1970-01-01
      • 2011-08-30
      • 2012-07-25
      • 1970-01-01
      • 1970-01-01
      • 2014-07-21
      • 1970-01-01
      相关资源
      最近更新 更多