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

Ale*_*lex 3 c# collections getter setter

我想要一个带有(例如)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";
   }
}
Run Code Online (Sandbox Code Playgroud)

更新:我想创建一个类System.Data.SqlClient.SqlCommand.对于存储过程,您可以使用填充"参数"集合的成员"DeriveParameters",因此只能修改每个项目的值.

如何才能做到这一点?

Dan*_*ker 7

如果要在编译时禁止修改操作,则需要类型安全的解决方案.

为公开允许的操作声明接口.使用该接口作为属性类型.

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

    int Count { get; }
}
Run Code Online (Sandbox Code Playgroud)

然后声明一个实现该接口的类,并继承自标准集合类.

public class SafeList<T> : List<T>, IReadOnlyList<T> { }
Run Code Online (Sandbox Code Playgroud)

假设您获得了正确的接口定义,您不需要手动实现任何东西,因为基类已经提供了实现.

使用该派生类作为存储属性值的字段的类型.

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

    public IReadOnlyList<string>
    {
        get { return _list; }
    }
}
Run Code Online (Sandbox Code Playgroud)

在A类中,您可以_list直接使用,因此修改内容.A类客户端只能使用可用的操作子集IReadOnlyList<T>.

对于您的示例,您使用的是SortedList而不是List,因此接口可能需要

public interface IReadOnlyDictionary<K, V> : IEnumerable<KeyValuePair<K, V>>
{
    V this[K index] { get; }        
}
Run Code Online (Sandbox Code Playgroud)

我已经让它继承了IEnumerable,无论如何都是readonly,所以非常安全.安全课程将是:

public class SafeSortedList<K, V> : SortedList<K, V>, IReadOnlyDictionary<K, V> { }
Run Code Online (Sandbox Code Playgroud)

但除此之外它也是一样的想法.

更新:刚刚注意到(由于某种原因,我无法理解)你不想禁止修改操作 - 你只是想禁止一些修改操作.很奇怪,但它仍然是相同的解决方案.无论您想要允许哪些操作,请在界面中"打开它们":

public interface IReadOnlyDictionary<K, V> : IEnumerable<KeyValuePair<K, V>>
{
    V this[K index] { get; set; }        
}
Run Code Online (Sandbox Code Playgroud)

当然,现在这个界面的名称是错误的...为什么你想要禁止通过添加添加但不通过索引器禁止它?(索引器可用于添加项目,就像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
}
Run Code Online (Sandbox Code Playgroud)

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


Jon*_*eet 6

编辑:原始答案如下.正如耳语所指出的那样,我没有注意到你并没有要求它只是为了只读 - 只是为了防止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";
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你必须找到一个ReadOnlyDictionary实现......我现在无法实现它,但如果有必要,我会稍后再回来......