使用用例来理解为什么应该将字符串列表声明为readonly

时间:2018-01-29 11:17:53

标签: c# clr

我试图了解哪些用例需要我声明List< string>作为ReadOnly类型。

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

3 个答案:

答案 0 :(得分:42)

将字段标记为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,因为不够:a {{1} }方法可以重写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()。因此体操和堆栈上的副本。通常这可以忽略不计,但如果结构非常大,那么这可能会导致性能问题。保证值不会发生变化的同样问题也适用于C#7.2中新的_foo参数修饰符:

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); } } ,因为这是引用类型,而不是值类型

答案 1 :(得分:0)

  

使用readonly,您可以在中设置字段的值   声明,或在该字段为a的对象的构造函数中   成员。

根据List,这意味着只有List对象的引用才是不可变的(不是内部字符串)。您可以readonly使用List只是为了确保不会覆盖字段引用。

这是一个示例Why does Microsoft advise against readonly fields with mutable values?

答案 2 :(得分:0)

在.NET中可以使用的所有类中,字符串到目前为止是最奇怪的行为。在许多情况下,String被设计为像值类型而不是引用类型一样操作。那是在我们添加字符串特定的东西,比如字符串实习到混合之前:

Comparing Values for Equality in .NET: Identity and Equivalence

所有人都说,我不知道为什么有人会标记一个列表&lt; string&gt; readonly,不仅仅是list [int]或list [object]。

分配了多少内存:这是不可预测的。当您向其添加项目时,列表将会增长。它将进行全面分配,以避免过度重新分配。但确切的算法是框架实现细节/类版本细节。如果您事先知道需要多少项,可以在构造函数中给出计数,或者只是从静态源集合(如数组)构建列表。这可以是最小的性能提升,通常是很好的做法。同时,字符串实习将尝试通过重用引用来限制分配给内存中实际字符串实例的内存量。