无法将类型 'System.Attribute' 转换为泛型参数 'T' - 为什么?

Cannot convert type 'System.Attribute' to generic argument 'T' - why?

我正在尝试将我的桌面 .NET dll 编译到 WinRT 平台(目标 Windows 8.1)。它在桌面上运行良好,但在 WinRT 项目中我在代码中遇到编译器错误:

internal static T[] CreateRuntime<T>(MemberInfo member, bool inherit)
{
    return member.GetCustomAttributes(typeof(T), inherit).Select(attr => (T)attr).ToArray();
}

我无法将 Attribute attr 转换为通用参数 T... 但为什么只在 WinRT 上?是否存在语言差异或可能导致此问题的原因?

这是 C# 中类型转换规则的结果。来自 C# 5.0 规范:

6.2.7 Explicit conversions involving type parameters
The following explicit conversions exist for a given type parameter T:
• From the effective base class C of T to T and from any base class of C to T. At run-time, if T is a value type, the conversion is executed as an unboxing conversion. Otherwise, the conversion is executed as an explicit reference conversion or identity conversion.

[…there are three other bullet points, but none apply here…]

The above rules do not permit a direct explicit conversion from an unconstrained type parameter to a non-interface type, which might be surprising. The reason for this rule is to prevent confusion and make the semantics of such conversions clear.

不受约束,T 的唯一已知基数 class 是 System.Object。但是在Winrt中,GetCustomAttributes()方法is implemented as an extension method, returning IEnumerable<Attribute> instead of the object[] that is returned in the .NET API。因此变量 attr 的类型是 Attribute,而不是 object.

因此,当您使用 .NET API 时,您可以从基础 class object 转换为 T,但是当您使用 Winrt 时,您不能从非基础-class Attribute 转换为 T.

您可以通过向方法声明添加约束来更改 T 的已知基数 class:

internal static T[] CreateRuntime<T>(MemberInfo member, bool inherit)
    where T : Attribute
{
    return member.GetCustomAttributes(typeof(T), inherit).Select(attr => (T)attr).ToArray();
}

由于 T 的基数 class 现在声明为 Attribute,您现在可以从 Attribute 转换为 T


请注意,这是 而不是 .NET 和 Winrt 之间的语言实现差异。这是完全相同的 C# 规则。很简单,您实际上是在处理 GetCustomeAttributes() 的不同实现,其中 return 值不同,从而使变量 attr 的类型不同,从而在每种情况下产生不同的结果。


查看相关问题:

Cannot convert type 'System.Windows.Forms.Control' to 'T'
C# Problem with Generics
Casting to generic type fails in c#

令我失望的是 none 这些其他方面相关的问题和答案解决了问题的核心,即 为什么 C# 提出此要求。我在上面尝试过这样做。但是,它们确实涵盖了相似的场景,展示了在非 Winrt 代码的上下文中如何发生完全相同的事情。他们还说明了两个基本解决方案:

  1. 对泛型参数使用约束(正如我在此处建议的那样)
  2. 在转换为 T 之前转换为 object。这绕过了泛型转换,删除了上下文,以便编译器允许转换。恕我直言,这不太可取;但是如果由于某种原因你不能添加约束(比如你正在处理一个特殊情况,它不适用于所有可能的情况T......但是这样的实现是不受欢迎的),这将起作用。