粗略地说,“out”的意思是“仅出现在输出位置”。
粗略地说,“in”的意思是“仅出现在输入位置”。
真实的故事比这要复杂一些,但选择关键词是因为大多数情况都是这样。
考虑接口的方法或委托表示的方法:
delegate void Foo</*???*/ T>(ref T item);
T出现在输入位置吗?是的。调用者可以在via item中传递T的值;被调用者 Foo 可以读取它。因此 T 不能被标记为“out”。
T出现在输出位置吗?是的。被调用者可以向 item 写入一个新值,然后调用者可以读取该值。因此 T 不能标记为“in”。
因此,如果 T 出现在“ref”形式参数中,则 T 不能被标记为 in 或 out。
让我们看一些实际的例子来说明问题是如何发生的。假设这是合法的:
delegate void X<out T>(ref T item);
...
X<Dog> x1 = (ref Dog d)=>{ d.Bark(); }
X<Animal> x2 = x1; // covariant;
Animal a = new Cat();
x2(ref a);
好吧,狗我的猫,我们刚刚发出了猫叫声。 “出去”不可能是合法的。
那么“在”呢?
delegate void X<in T>(ref T item);
...
X<Animal> x1 = (ref Animal a)=>{ a = new Cat(); }
X<Dog> x2 = x1; // contravariant;
Dog d = new Dog();
x2(ref d);
我们只是将一只猫放入一个只能容纳狗的变量中。 T 也不能标记为“in”。
那么输出参数呢?
delegate void Foo</*???*/T>(out T item);
?现在T只出现在输出位置。将 T 标记为“out”是否合法?
很不幸的是,不行。 “out”实际上与幕后的“ref”没有什么不同。 “out”和“ref”之间的唯一区别是compiler禁止在被调用者分配之前读取输出参数,并且编译器要求在被调用者正常返回之前进行分配。有人编写了该接口的实现使用 C# 以外的 .NET 语言能够在初始化之前读取该项目,因此可以将其用作输入。因此,在这种情况下,我们禁止将 T 标记为“out”。这是令人遗憾的,但我们对此无能为力;我们必须遵守 CLR 的类型安全规则。
此外,“out”参数的规则是它们不能用于输入在它们被写入之前。没有规定不能用于输入after他们被写给。假设我们允许
delegate void X<out T>(out T item);
class C
{
Animal a;
void M()
{
X<Dog> x1 = (out Dog d) =>
{
d = null;
N();
if (d != null)
d.Bark();
};
x<Animal> x2 = x1; // Suppose this were legal covariance.
x2(out this.a);
}
void N()
{
if (this.a == null)
this.a = new Cat();
}
}
我们再一次发出了猫叫声。我们不能让T“出局”。
以这种方式使用out参数进行输入是非常愚蠢的,但却是合法的。
更新:C# 7 已添加in
作为形式参数声明,这意味着我们现在拥有in
and out
意味着两件事;这会造成一些混乱。让我澄清一下:
-
in
, out
and ref
on a 参数列表中的形式参数声明意思是“这个参数是调用者提供的变量的别名”。
-
ref
意思是“被调用者可以读取或写入别名变量,并且必须在调用之前知道它已被赋值。
-
out
意思是“被调用者必须通过别名写入别名变量才能正常返回”。这也意味着被调用者在写入别名变量之前不得通过别名读取别名变量,因为该变量可能未明确分配。
-
in
意思是“被调用者可以读取别名变量,但不能通过别名写入它”。的目的in
是为了解决一个罕见的性能问题,即必须“按值”传递大型结构,但这样做的成本很高。作为实施细节,in
参数通常通过指针大小的值传递,这比按值复制更快,但在取消引用时速度较慢。
- 从 CLR 的角度来看,
in
, out
and ref
都是同一件事;关于谁在什么时间读取和写入什么变量的规则,CLR 不知道也不关心。
- 由于 CLR 强制执行有关方差的规则,因此适用于
ref
也适用于in
and out
参数。
相比之下,in
and out
on 类型参数声明分别表示“此类型参数不得以协变方式使用”和“此类型参数不得以逆变方式使用”。
如上所述,我们选择了in
and out
对于这些修饰符,因为如果我们看到IFoo<in T, out U>
then T
用于“输入”位置并且U
用于“输出”位置。虽然那不是strictly确实,在 99.9% 的用例中,它确实是一个有用的助记符。
不幸的是interface IFoo<in T, out U> { void Foo(in T t, out U u); }
是非法的,因为它看起来应该有效。它无法工作,因为从 CLR 验证程序的角度来看,这些都是ref
参数,因此可读写。
这只是那些奇怪的、意想不到的情况之一,逻辑上应该协同工作的两个功能由于实现细节原因而不能很好地协同工作。