2

假设我有这个:

public class MyClass
{
    private readonly List<string> _strings = new List<string>();

    public IEnumerable<string> MyStrings 
    { 
        get 
        { 
            return _strings; 
        }
    }
}

如何防止以下内容实际添加到字符串的成员变量列表中?

var theClass = new MyClass();
((List<string>)theClass.MyStrings).Add("I do not want to be able to add this!");
4

4 回答 4

8

创建一个“包装”序列的新类型。虽然有很多方法可以做到这一点,但一个相当简单的“hack”就是Select在其上使用身份转换:

return _strings.Select(x=>x); 

这将使用它自己的迭代器创建一个新对象,该迭代器在内部具有对_strings列表的引用。由于额外的间接层(并且类型的额外好处是语言内部的类型,在语言定义之外使用无效),调用者不能再将他们的方式投射到列表中。

如果您想要一些更清晰的阅读内容,从而使意图更加清楚,您可以为其创建一个新方法:

public static IEnumerable<T> AsSequence<T>(this IEnumerable<T> sequence)
{
    return sequence.Select(x => x);
}

(请随意命名您最清楚的名称。)

于 2013-11-05T22:21:52.527 回答
4

根据您打算如何使用此属性,您可以尝试以下方法之一:

public IEnumerable<string> MyStrings
{ 
    get 
    { 
        foreach (var s in _strings)
            yield return s; 
    }
}

或者

public IEnumerator<string> MyStringsEnumerator
{ 
    get 
    { 
        return _strings.GetEnumerator();
    }
}

两者都在列表上返回一个枚举器,因此这些属性的用户不能更改列表。

第一个使用yield关键字创建一个新的迭代器。它对 的用户很方便MyStrings,因为它返回一个IEnumerable<string>,因此可以在其上使用 LINQ。

第二个返回内置的List<T>枚举器。它返回一个IEnumerator<string>,因此使用起来不太方便。

如果此代码将在多线程应用程序中使用,或者如果使用此属性的代码不在您的控制范围内,请务必阅读 MSDN 上的文档 - 特别注意现有迭代器是如何失效的,并且在更改基础集合时具有未定义的行为。

于 2013-11-05T22:28:45.690 回答
2

有一种常见的公开方法ReadOnlyCollection<T>

private readonly List<string> _strings = new List<string>();

public IEnumerable<string> MyStrings 
{ 
    get 
    { 
        return new ReadOnlyCollection<string>(_strings);
    }
}

如果您尝试调用以下代码,您将收到异常。

var theClass = new MyClass();
((List<string>)theClass.MyStrings).Add("I do not want to be able to add this!");
于 2013-11-05T22:21:38.573 回答
-1

返回集合的副本。那么你的类的消费者是否添加到它并不重要,如果你弄乱了你的列表副本也没关系。

private readonly List<string> _strings = new List<string>();

public IEnumerable<string> MyStrings 
{ 
    get 
    { 
        return new List<string>(_strings);
    }
}
于 2013-11-05T22:32:52.833 回答