如何仅保护访问者IEnumerable <string>属性不发生突变?</string>

时间:2013-11-05 22:19:19

标签: c# generics ienumerable

假设我有这个:

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 个答案:

答案 0 :(得分:8)

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

return _strings.Select(x=>x); 

这将使用它自己的迭代器创建一个新对象,该迭代器在内部具有对_strings列表的引用。由于额外的间接层(并且该类型的额外奖励是语言内部的类型,无法在语言定义之外使用),调用者无法再进入列表。

如果你想要一些更清晰的东西来阅读,使意图更加为人所知,你可以为它做一个新的方法:

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

(请随意将其命名为最清楚的。)

答案 1 :(得分: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上的文档 - 特别注意现有迭代器如何失效并且在基础集合时具有未定义的行为改变。

答案 2 :(得分: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!");

答案 3 :(得分:-1)

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

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

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