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

发布于 2024-07-26 17:20:40 字数 779 浏览 11 评论 0原文

我想要一个带有(例如)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”来填充“Parameters”集合,因此只能修改每个项目的值。

如何才能做到这一点?

I'd like to have a class "A" with a (for example) SortedList collection "SrtdLst" property, and inside this class "A" allow the addition or subtraction of "SrtdLst" items. But in a instance of the class "A", only allow to get or set the content of the items, not to add new items or subtract the existing ones. In code:

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: I want to create a class like System.Data.SqlClient.SqlCommand. For the Stored Procedures you can use the member "DeriveParameters" that fills a collection of "Parameters", so only the value of each item can be modified.

How can this be done?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

云仙小弟 2024-08-02 17:20:40

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

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

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 提供的操作子集。

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

任何时候您有一个普通字典,并且想要将其交给您不信任的某个方法来更新现有值,请将其包装在其中之一中。

If you want to ban the modifying operations at compile time, you need a type-safe solution.

Declare an interface for the publicly allowed operations. Use that interface as the property type.

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

    int Count { get; }
}

Then declare a class that implements that interface and inherits from the standard collection class.

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

Assuming you get the interface definition right, you won't need to implement anything by hand, as the base class already provides the implementations.

Use that derived class as the type of the field that stores the property value.

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

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

Within class A, you can use _list directly, and so modify the contents. Clients of class A will only be able to use the subset of operations available via IReadOnlyList<T>.

For your example, you're using SortedList instead of List, so the interface probably needs to be

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

I've made it inherit IEnumerable as well, which is readonly anyway, so is perfectly safe. The safe class would then be:

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

But otherwise it's the same idea.

Update: just noticed that (for some reason I can't fathom) you don't want to ban modifying operations - you just want to ban SOME modifying operations. Very strange, but it's still the same solution. Whatever operations you want to allow, "open them up" in the interface:

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

Of course, that's the wrong name for the interface now... why on earth would you want to ban adding via Add but not ban it via the indexer? (The indexer can be used to add items, just as the Add method can.)

Update

From your comment I think you mean that you want to allow assignment to the value of an existing key/value pair, but disallow assignment to a previously unknown key. Obviously as keys are specified at runtime by strings, there's no way to catch that at compile time. So you may as well go for runtime checking:

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
}

Any time you have an ordinary dictionary and you want to hand it to some method that you don't trust to update the existing values, wrap it in one of these.

回忆凄美了谁 2024-08-02 17:20:40

编辑:原始答案如下。 正如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 实现...我现在无法实现它,但如果需要的话我稍后会回来...

EDIT: Original answer is below. As earwicker points out, I hadn't noticed that you aren't asking for it to be readonly - just to prevent the Add operation. That doesn't sound like a good idea to me, as the only difference between Add and the indexer-setter is that Add throws an exception if the element is already present. That could easily be faked up by the caller anyway.

Why do you want to restrict just that one operation?


Original answer

For one thing, don't use public fields. That's a surefire way to run into problems.

It looks like you want a read-only wrapper class round an arbitrary IDictionary. You can then have a public property which returns the wrapper, while you access the private variable from within your class. For example:

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

Now you've just got to find a ReadOnlyDictionary implementation... I can't implement it right now, but I'll be back later if necessary...

寒尘 2024-08-02 17:20:40

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

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

但是,由于列表的索引器允许创建新项目,因此您必须在索引器中添加代码以防止出现这种情况如果你不想那样做也是可能的。

Just make the list private, and expose it as an indexer:

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

}

Now you can only access the items using the index:

a["KeyA"] = "ValueBBB";

However, as the indexer of the list allows creation of new items, you would have to add code in the indexer to prevent that if you don't want that do be possible.

╰◇生如夏花灿烂 2024-08-02 17:20:40

如果键在类外部已知,则可以将 ChangeItem(key, newValue) 和 ReadItem(key) 添加到包装类中。 然后将 SortedList 保留为类的私有。

If the keys are known outside of the class then you can add a ChangeItem(key, newValue) and ReadItem(key) to your wrapper class. Then keep the SortedList private to the class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文