书中第二个例子的方向是从动态到对象,对吗?第一个示例具有从字符串到对象的方向,我认为第二个示例将从对象到动态。
为了将其转化为具体示例,让我们考虑一下
。规则含义如下:Func<in T, out TResult>
存在从
到Func<object, int>
的有效转换,因为Func<string, int>
- 第一个类型参数是逆变的,并且存在隐式引用转换从字符串到对象。
- 第二个类型参数是协变的,并且存在从 int 到 int 的恒等转换。
存在从
到Func<dynamic, string>
的有效转换,因为Func<object, IConvertible>
- 第一个类型参数是逆变的,并且存在从动态到对象的恒等转换。
- 第二个类型参数是协变的,并且存在从字符串到 IConvertible 的隐式引用转换
— 代码片段取自 J. Skeet 所著的《C# in Depth》第四版,第 147 页。
为了简单起见,我们考虑委托仅采用参数:
Action<in TParam>
。我们可以将其视为一种方法:
void SomeMethod(TParam param);
或更具体地说:
void SomeMethod(string param);
因此,将此类方法分配给变量是完全有效的,例如并调用它:
Action<string> action = SomeMethod;
action("some value");
现在,让我们考虑一下
action
变量 - 它接受字符串参数,但是如果该变量引用 Action<object>
呢?如下:
// Note changed type of param
void SomeMethod(object param);
Action<string> action = SomeMethod;
// IMPORTANT - this is still perfectly valid
action("some value");
它仍然可以工作,因为
action
被声明为仅接受 string
的方法,因此只要 string
派生自,那么底层的内容(什么是 true 类型参数)并不真正让我们担心那种类型。
这就是逆变的全部内容,直线
Action<string> action = SomeMethod;
如果 SomeMethod
的参数具有
object
类型,则仍然有效。
更简单。为了简单起见,让我们考虑一下
Func<out TResult>
。
TResult
是委托的返回类型。因此,如果方法返回 object
,它也可以返回较少的派生类型,并且结果仍然可以分配给基本类型,即:
Func<object> d1 = () => new object();
Func<string> d2 = () => "covariance";
现在,当你调用
d1()
时,你期望得到一个对象,当你调用 d2()
时,你期望得到一个字符串。由于 string
派生自 object
,如果该方法返回更多派生类型(例如字符串),d1
的调用者不会中断。所以作业:
d1 = d2;
可以工作,因为返回类型仍然兼容。
这要归功于协方差(另一个例子可能是集合)。
我希望很清楚,现在
Func<TInput, TResult>
和其他代表(具有更多类型参数)只是我上面解释的一些组合:)
回到你的问题。
Func
委托中的第一个类型参数(当有两个以上类型参数时)是逆变的(带有 in
修饰符)。这意味着派生较少的类型可以分配给使用派生较多的类型定义的变量。所以:
Func<object, int>
到 Func<string, int>
- 正确,因为 Func<object, int>
可以分配给任何 Func<whatever, int>
,因为 whatever
源自 object
Func<dynamic, string>
到 Func<object, IConvertible>
- 正确,因为 Func<dynamic, int>
可以分配给任何 Func<object, int>
,因为 object
可以分配给 dynamic
。