Visual Studio 建议我将结构上的方法设为只读,这是什么意思?我以为只有字段可以是只读的,方法不是。
public struct MyStruct {
...
// I have this
public void MyMethod() { ... }
// VS wants me to write this
public readonly void MyMethod() { ... }
}
我在 MSDN 上确实找不到任何关于标记为只读的方法的清晰描述,它都是关于只读参数或ref readonly
返回值的:
显然,这并不意味着返回值变为只读,因为我返回的是 void。在我看来,效果是this
现在变为只读,但我还想知道这是否意味着它现在与 之in
类的参数相同ExtMethod(this in MyStruct self)
,在这种情况下,结构是通过引用传递的。
具体来说: 只读结构方法是否保证this
通过引用传递,或者它是否仍然被完全复制(由于是值类型)就像结构通常传递给方法时一样?
,事实证明我的问题的前提是错误的,结构上的方法调用已经是通过引用进行的。
3
最佳答案
3
请考虑以下情况:
public struct MyStruct
{
public void MyMethod1() { }
public readonly void MyMethod2() { }
}
public static class Test
{
private static MyStruct field1;
private static readonly MyStruct field2;
public static void ExtMethod1(this in MyStruct self) => self.MyMethod1();
public static void ExtMethod2(this in MyStruct self) => self.MyMethod2();
public static void ExtMethod3(this MyStruct self) => self.MyMethod2();
public static void ExtMethod4(this ref MyStruct self) => self.MyMethod1();
public static void ExtMethod5() => field1.MyMethod1();
public static void ExtMethod6() => field2.MyMethod1();
public static void ExtMethod7() => field2.MyMethod2();
}
- 在
ExtMethod1
其中声明了 ,但self
不应该这样change
。但是编译器发现该结构是可变的,而且我正在调用它的方法,因此它必须创建一个防御性副本。这可能与预期相反,所以至少我的 IDE 会对此发出警告。 - 仅调用
ExtMethod2
只读方法,因此不需要防御性复制,因为编译器可以确保它没有被修改。 - 由于
ExtMethod3
参数未标记为in
或ref
,因此将进行复制。 - 其中
ExtMethod4
明确声明了self
可能会发生变异,因此不会进行复制。 - 由于
ExtMethod5
该领域是可变的,因此无法进行复制,因为变异是故意的。 ExtMethod6
必须进行复制以避免变异。与相同ExtMethod1
。- 字段和方法都是
ExtMethod7
只读的,因此不需要复制。与 相同ExtMethod2
。
请注意,只要编译器能够证明行为不会改变,它就可以自由地进行任何优化。但是很难证明一个方法可以做什么,所以编译器有时需要开发人员的注释来确定它可以优化什么,不能优化什么。因此,虽然ExtMethod3
可以删除复制,但没有任何保证,您需要检查汇编程序以确定它是否真的完成了。
3
-
举一个简单的例子来说明这种影响:想象一个
readonly DateTime when
字段;在之前readonly
,即使是像 这样的内容var end = when.Add(offset);
也会创建防御性副本,以确保正确性。我有旧代码,其中有这样的注释// note: not marked readonly for performance (defensive copy)
– -
1我问的是它
this
是如何传递到方法中this
的,而不是它是否先进行防御性复制。它是作为值复制到方法中还是作为引用传递?’in’ 保证标记的参数通过引用传递。只读方法是否保证不可见参数也通过引用传递?
– -
@Sander 如果
this
复制了某个位置,则会导致无法使用改变结构的方法,因为调用者永远不会看到改变的副本。因此,除非在只读结构上调用可变方法,否则仅调用成员方法不会创建副本。
–
|
简而言之; readonly
在结构方法上意味着this
将其作为只读引用传递给方法,而不是像非只读结构方法那样作为普通引用。它还确保在只读变量上调用此方法时不需要进行防御性复制。
本人更正
看来我一开始就错误地认为结构是通过值传递给它们自己的方法的。
检查了一些代码示例的编译器输出(在 .NET 8.0 中,调试 – 因此它们没有经过优化),似乎在所有对结构进行方法调用的情况下,结构都是通过引用传递给方法而不是通过值。
public static void CallOnValue(MyStruct mystruct) // passed by value
{
mystruct.NormalMethod(); // passed by reference
}
/* Generated CIL:
ldarga.s mystruct
call instance void ProjName.MyStruct::NormalMethod()
*/
// ldarga = load arg address
// So, the address (= reference) of mystruct is passed to the method
回答
所以是的,只读方法this
通过引用传递,但其他方法也是如此。
这样,常规结构体方法的行为实际上(*) 与ref
扩展方法相同,而只读结构体方法的行为实际上(*) 与扩展方法相同in
。
public struct MyStruct
{
public void NormalMethod() { }
public readonly void ReadonlyMethod() { }
}
public static class Test
{
public static void ExtSameAsNormal(this ref MyStruct self) { }
public static void ExtSameAsReadonly(this in MyStruct self) { }
}
* 请注意,实例方法和静态方法发出的 IL 略有不同:
call instance void ProjName.MyStruct::NormalMethod()
call void ProjName.Test::ExtSameAsNormal(valuetype ProjName.MyStruct&)
但在所有 4 个示例中,结构都是通过引用传递给方法的。对于扩展方法,您只需明确指定“in”或“ref”即可获得引用语义,而实例方法则隐式地执行此操作。
关于防御性拷贝
对于普通方法,如果在只读引用上调用它们,编译器无法确保该方法不会修改结构,因此它会进行“防御性复制”并将该引用传递给该方法,以保证原始结构不会被修改。它有效地做到了这一点:
public static void Foo(in MyStruct readonlyStruct) // 'in' = passed as readonly reference
{
readonlyStruct.NormalMethod();
}
// Generates this code when compiled:
public static void Foo(in MyStruct readonlyStruct)
{
MyStruct defCopy = readonlyStruct;
defCopy.NormalMethod(); // defCopy may be modified, but readonlyStruct won't
}
通过标记您的方法readonly
,该方法声明它不会修改结构,因此readonlyStructVar.ReadonlyMethod();
不需要进行防御性复制。
另外有趣的是,如果你真的想避免防御性副本,你可能实际上更喜欢带有ref
参数的扩展方法而不是普通方法,因为它们不会生成防御性副本而是错误:
public static void CallOnIn(in MyStruct s)
{
s.NormalMethod(); // will make defensive copy without your knowledge
s.ExtSameAsNormal(); // Error: CS8329 Cannot use variable 's' as a ref or out value because it is a readonly variable
s.ReadonlyMethod(); // no copy needed
s.ExtSameAsReadonly(); // no copy needed
}
|
它只是意味着该方法不能改变结构的内容(字段、属性)。
1
-
但事情远不止于此。JonasH 的回答对此进行了更深入的介绍。
–
|
–
this
通过引用传递还是通过值传递。对于大型结构,我不希望它们通过值传递,因此我一直在使用具有显式in
参数的扩展方法。如果只读方法可以消除这种不必要的麻烦,那就太好了,那么我就不需要使用扩展方法了。–
–
|