我试图了解哪些用例需要我将List 声明为ReadOnly类型。

一个与此相关的问题是:在实例化列表时分配了多少内存?

最佳答案

将字段标记为readonly的主要原因是,您知道常规代码不能交换列表引用。这可能很重要的一个关键场景是,如果您有其他类型的代码正在使用lock(theListField)对列表执行同步。显然,如果有人交换了列表实例:事情将会中断。请注意,在大多数具有列表/集合的类型中,不希望更改实例,因此此readonly声明了这种期望。常见的模式是:

private List<Foo> _items = new List<Foo>();
public List<Foo> Items => _items;

或者:
public List<Foo> Items {get;} = new List<Foo>();

在第一个示例中,最好将该字段标记为readonly:
private readonly List<Foo> _items = new List<Foo>();

将字段标记为readonly不会影响分配等。它也不会使列表为只读:仅是字段。您仍然可以Add()/Remove()/Clear()等。您唯一不能做的就是将列表实例更改为完全不同的列表实例。当然,您仍然可以完全更改内容。无论如何,只读也是一个谎言:反射和不安全的代码可以修改readonly字段的值。

在一种情况下,readonly可能会产生负面影响,这与较大的struct字段及其调用方法有关。如果该字段是readonly,则编译器在调用该方法之前将结构复制到堆栈上-而不是在该字段中就地执行该方法; ldfld + stloc + ldloca(如果该字段是readonly)vs ldflda(如果未标记readonly);这是因为编译器无法信任不更改值的方法。它甚至无法检查结构上的所有字段是否都是readonly,因为这还不够:struct方法可以重写this :
struct EvilStruct
{
    readonly int _id;
    public EvilStruct(int id) { _id = id; }
    public void EvilMethod() { this = new EvilStruct(_id + 1); }
}

因为编译器正在尝试强制使用字段的readonly性质,所以如果您具有:
readonly EvilStruct _foo;
//...
_foo.EvilMethod();

它想确保EvilMethod()不能用新值覆盖_foo。因此,体操和复制品就在堆栈上。通常,其影响可忽略不计,但是如果结构非典型大,则可能会导致性能问题。保证值不变的问题同样适用于C#7.2中新的in参数修饰符:
void(in EvilStruct value) {...}

调用者想保证它不会更改值的地方(这实际上是ref EvilStruct,因此更改将被传播)。

在C#7.2中,通过添加readonly struct语法解决了此问题-这告诉编译器可以安全地就地调用该方法而不必进行额外的堆栈复制:
readonly struct EvilStruct
{
    readonly int _id;
    public EvilStruct(int id) { _id = id; }
    // the following method no longer compiles:
    // CS1604   Cannot assign to 'this' because it is read-only
    public void EvilMethod() { this = new EvilStruct(_id + 1); }
}

整个情况不适用于List<T>,因为那是引用类型,而不是值类型。

关于c# - 用例了解为什么应将字符串列表声明为只读,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48500018/

10-13 01:47