用例来理解为什么字符串列表应该被声明为只读
Use case to understand why a list of strings should be declared as readonly
我想了解哪些用例需要我将 List 声明为 ReadOnly 类型。
与此相关的一个问题是:在实例化列表时分配了多少内存?
Using readonly you can set the value of the field either in the
declaration, or in the constructor of the object that the field is a
member of.
根据 List
这意味着只有对 List
对象的引用是不可变的(不是内部字符串)。您可以将 readonly
用于 List
,只是为了确保字段引用不会被覆盖。
这里有一个例子Why does Microsoft advise against readonly fields with mutable values?
在您可以在 .NET 中使用的所有 类 中,字符串具有 迄今为止 最奇怪的行为。在许多情况下,String 被设计为像值类型而不是引用类型一样操作。那是在我们添加特定于字符串的东西(如字符串实习)之前:
Comparing Values for Equality in .NET: Identity and Equivalence
综上所述,我不知道为什么有人会将 list 标记为只读,而不是将 list[int] 或 list[object] 标记为只读。
分配了多少内存:这是不可预测的。该列表将随着您向其中添加项目而增长。并且它会overallocate,避免过多的重新分配。但确切的算法是框架实现 detail/class 版本细节。如果您提前知道需要多少项,请在构造函数中给出计数,或者只从静态源集合(如数组)构建列表。这可能是最小的性能提升,通常是一种很好的做法。同时,字符串驻留将尝试通过重用引用来限制分配给内存中实际字符串实例的内存量。
将字段标记为 readonly
的主要原因是让您知道常规代码不能交换列表引用。一个可能重要的关键场景是,如果您在使用 lock(theListField)
对列表执行同步的类型中有其他代码。显然,如果有人 交换 列表实例:事情就会崩溃。请注意,在大多数 具有 和 list/collection 的类型中,预计不会 更改 实例,因此此 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
对分配等没有影响。它也不会使 list 只读:只是字段。您仍然可以 Add()
/ Remove()
/ Clear()
等。您唯一不能做的是 将列表实例更改为完全不同的列表实例 ;当然,您仍然可以完全更改内容。无论如何,只读是一个谎言:反射和不安全代码可以修改 readonly
字段的值。
是一种情况,其中readonly
可能产生负面影响,这与大型struct
字段和对它们的调用方法有关。如果字段是 readonly
,编译器 在调用方法之前将结构复制到堆栈 - 而不是在字段中就地执行方法; ldfld
+ stloc
+ ldloca
(如果字段是readonly
)vs ldflda
(如果没有标记readonly
);这是因为编译器不相信不改变值的方法。它甚至无法检查结构 上的所有 字段是否都是 readonly
,因为 这还不够 : a 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>
,因为那是 引用类型 ,而不是 值类型 .
我想了解哪些用例需要我将 List
与此相关的一个问题是:在实例化列表时分配了多少内存?
Using readonly you can set the value of the field either in the declaration, or in the constructor of the object that the field is a member of.
根据 List
这意味着只有对 List
对象的引用是不可变的(不是内部字符串)。您可以将 readonly
用于 List
,只是为了确保字段引用不会被覆盖。
这里有一个例子Why does Microsoft advise against readonly fields with mutable values?
在您可以在 .NET 中使用的所有 类 中,字符串具有 迄今为止 最奇怪的行为。在许多情况下,String 被设计为像值类型而不是引用类型一样操作。那是在我们添加特定于字符串的东西(如字符串实习)之前:
Comparing Values for Equality in .NET: Identity and Equivalence
综上所述,我不知道为什么有人会将 list
分配了多少内存:这是不可预测的。该列表将随着您向其中添加项目而增长。并且它会overallocate,避免过多的重新分配。但确切的算法是框架实现 detail/class 版本细节。如果您提前知道需要多少项,请在构造函数中给出计数,或者只从静态源集合(如数组)构建列表。这可能是最小的性能提升,通常是一种很好的做法。同时,字符串驻留将尝试通过重用引用来限制分配给内存中实际字符串实例的内存量。
将字段标记为 readonly
的主要原因是让您知道常规代码不能交换列表引用。一个可能重要的关键场景是,如果您在使用 lock(theListField)
对列表执行同步的类型中有其他代码。显然,如果有人 交换 列表实例:事情就会崩溃。请注意,在大多数 具有 和 list/collection 的类型中,预计不会 更改 实例,因此此 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
对分配等没有影响。它也不会使 list 只读:只是字段。您仍然可以 Add()
/ Remove()
/ Clear()
等。您唯一不能做的是 将列表实例更改为完全不同的列表实例 ;当然,您仍然可以完全更改内容。无论如何,只读是一个谎言:反射和不安全代码可以修改 readonly
字段的值。
是一种情况,其中readonly
可能产生负面影响,这与大型struct
字段和对它们的调用方法有关。如果字段是 readonly
,编译器 在调用方法之前将结构复制到堆栈 - 而不是在字段中就地执行方法; ldfld
+ stloc
+ ldloca
(如果字段是readonly
)vs ldflda
(如果没有标记readonly
);这是因为编译器不相信不改变值的方法。它甚至无法检查结构 上的所有 字段是否都是 readonly
,因为 这还不够 : a 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>
,因为那是 引用类型 ,而不是 值类型 .