为什么递增运算符可以在属性上使用,而 ref 不行
Why are increment operators possible on properties while ref is not
假设你有一个 class 这样的
public class Foo
{
public int Bar { get; set; } = 42;
}
如果您尝试将 属性 作为 ref
参数传递,编译器会发出错误
CS0206 A property or indexer may not be passed as an out or ref
parameter
这是可以理解的,因为实际上上面示例中的 属性 被编译成 get_Bar()
和 set_Bar()
方法。但是如果你在 属性 上使用增量运算符,比如
var foo = new Foo();
foo.Bar++;
它按预期工作。为此,编译器需要生成如下伪代码:
var foo = new Foo();
int tmp = foo.get_Bar();
tmp++;
foo.set_Bar(tmp);
所以理论上编译器可以为 ref
做类似的事情,比如:
var foo = new Foo();
int tmp = foo.get_Bar();
DoSomething(ref tmp);
foo.set_Bar(tmp);
编译器不这样做是否有技术原因,或者这只是 C# 团队的设计决定?
正如 HansPassant 所说,这是 C# 团队在编写 C# 规范时做出的设计决定,因此您必须询问其中一位才能获得正确答案。
不过,如果我冒险猜测的话,编译器将 属性 与 ref
传递的魔法数量将导致在幕后发生足够多的不明显操作使解决方案不受欢迎。例如,incrementing/decrementing a 属性 目前的工作方式如您所说:程序将 属性 的支持字段的值分配给临时变量,执行操作,然后重新分配结果为 属性。这是一个简单的过程,没有包含任何困难的概念。
要在幕后做同样的魔术,将 属性 传递给 ref
,但是,这个过程变得有点复杂。当ref
传递值类型时,通过参数传递的实际值是指向值类型变量的指针。但是,要为 属性 执行此操作,您必须执行类似于第二个示例的操作。这将导致临时变量的地址,而不是 属性 本身,被传递给方法。对于试图以某些方式操纵 ref
参数的人来说,这种行为可能会导致一些无法预料且难以理解的后果。
所以我猜测,增量运算符很容易包装,因为它只处理值,而 ref
关键字更复杂,因为它还必须担心范围和内存地址。
编辑: 我想到的另一个原因是,对于一个字段,被调用方法内部的任何操作都会反映在该字段本身上。这些操作可以被其他线程看到,从而在方法执行期间访问该字段(撇开并发字段可访问性的最佳实践)。
但是,对于参数,在方法返回并且值被复制回来之前,方法内部发生的任何更改都是不可见的。这将导致字段和属性之间的行为不一致,其原因并不明显。
(个人认为这是不支持 ref
属性的更可能的原因。)
假设你有一个 class 这样的
public class Foo
{
public int Bar { get; set; } = 42;
}
如果您尝试将 属性 作为 ref
参数传递,编译器会发出错误
CS0206 A property or indexer may not be passed as an out or ref parameter
这是可以理解的,因为实际上上面示例中的 属性 被编译成 get_Bar()
和 set_Bar()
方法。但是如果你在 属性 上使用增量运算符,比如
var foo = new Foo();
foo.Bar++;
它按预期工作。为此,编译器需要生成如下伪代码:
var foo = new Foo();
int tmp = foo.get_Bar();
tmp++;
foo.set_Bar(tmp);
所以理论上编译器可以为 ref
做类似的事情,比如:
var foo = new Foo();
int tmp = foo.get_Bar();
DoSomething(ref tmp);
foo.set_Bar(tmp);
编译器不这样做是否有技术原因,或者这只是 C# 团队的设计决定?
正如 HansPassant 所说,这是 C# 团队在编写 C# 规范时做出的设计决定,因此您必须询问其中一位才能获得正确答案。
不过,如果我冒险猜测的话,编译器将 属性 与 ref
传递的魔法数量将导致在幕后发生足够多的不明显操作使解决方案不受欢迎。例如,incrementing/decrementing a 属性 目前的工作方式如您所说:程序将 属性 的支持字段的值分配给临时变量,执行操作,然后重新分配结果为 属性。这是一个简单的过程,没有包含任何困难的概念。
要在幕后做同样的魔术,将 属性 传递给 ref
,但是,这个过程变得有点复杂。当ref
传递值类型时,通过参数传递的实际值是指向值类型变量的指针。但是,要为 属性 执行此操作,您必须执行类似于第二个示例的操作。这将导致临时变量的地址,而不是 属性 本身,被传递给方法。对于试图以某些方式操纵 ref
参数的人来说,这种行为可能会导致一些无法预料且难以理解的后果。
所以我猜测,增量运算符很容易包装,因为它只处理值,而 ref
关键字更复杂,因为它还必须担心范围和内存地址。
编辑: 我想到的另一个原因是,对于一个字段,被调用方法内部的任何操作都会反映在该字段本身上。这些操作可以被其他线程看到,从而在方法执行期间访问该字段(撇开并发字段可访问性的最佳实践)。
但是,对于参数,在方法返回并且值被复制回来之前,方法内部发生的任何更改都是不可见的。这将导致字段和属性之间的行为不一致,其原因并不明显。
(个人认为这是不支持 ref
属性的更可能的原因。)