规则是事件的类型必须是输入安全。这与方法参数相同。这是有道理的,因为当您在界面减速中声明事件时,您实际上是在声明一对add
and remove
访问器,看起来像这样:
void add_MyEventTake(MyDelegateTake<T> value);
void remove_MyEventTake(MyDelegateTake<T> value);
当你使用时这些将会被调用+=
and -=
关于活动。
因此,为了简化事情,我们将考虑接受的方法MyDelegateTake
and MyDelegateReturn
.
interface IMyInterfaceReturn<out T>
{
T Get();
void add_MyEventTake(MyDelegateTake<T> t);
}
有一个section https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/interfaces#variance-safety在描述规则的 C# 规范中。如果您愿意,您可以查看规则,但我会尝试更直观地解释这一点。
在本节的最后,描述了如何直观地解释“输入安全”和“输出安全”:
直观地,在输出位置禁止输出不安全类型,并且在输入位置禁止输入不安全类型。
如果类型不是输出不安全,则它是输出安全的;如果它不是输入不安全,则它是输入安全。
In IMyInterfaceReturn
, T
是协变的,因此输出安全,因此只能在“输出位置”使用。我现在将显示该参数MyDelegateTake<T>
确实是一个“输出位置”。
假设你有一个实现IMyInterfaceReturn<string>
called Foo
:
class Foo : IMyInterfaceReturn<string>
{
public void add_MyEventTake(MyDelegateTake<string> t)
{
t(Get());
}
public string Get()
{
return "Something produced by Foo";
}
}
您可以使用Foo
像这样:
var foo = new Foo();
foo.add_MyEventTake(str => /* do something with str */);
你不同意吗str
是某种“输出”Foo
?上面的代码实际上就是以下内容,但以一种更间接的方式:
var foo = new Foo();
var str = foo.Get();
// do something with str...
因此,可以得出T
处于输出位置,您可以安全地执行诸如分配之类的操作foo
到一个类型的变量IMyInterfaceReturn<object>
.
If add_MyEventTake
took a MyDelegateReturn
然而,add_MyEventTake
将是一个consumer of T
, and T
将处于输入位置。Foo
可以这样使用:
var foo = new Foo();
foo.add_MyEventTake(() => "Some Input For Foo!");
// this is just a roundabout way of doing:
foo.add_MyEventTake("Some Input Fo Foo!");
并假设Foo
是这样实现的:
class Foo : IMyInterfaceReturn<string>
{
private string s;
public void add_MyEventTake(MyDelegateReturn<string> t)
{
s = t();
}
public string Get()
{
return s.ToUpper();
}
}
那么这段代码就会崩溃:
IMyInterfaceReturn<object> foo = new Foo();
foo.add_MyEventTake(() => new object());
foo.Get(); // what would this do? object has no ToUpper!?