为什么 C# 编译器允许使用 Linq 执行强制转换,但不允许使用括号执行转换

本文关键字:转换 执行 不允许 编译器 许使用 Linq 为什么 | 更新日期: 2023-09-27 18:30:23

我有一个泛型类NamedValue<TValue>

public class NamedValue<TValue>
{
    public string Name { get; set; }
    public TValue Value { get; set; }
}

我有第二个泛型类,NamedValueSource<TValue>包含一个List<NamedValue<TValue>>

public class NamedValueSource<TValue>
{
    public List<NamedValue<TValue>> NamedValues { get; set; }
    public NamedValueSource()
    {
        NamedValues = GetNamedValues().Cast<NamedValue<TValue>>().ToList();
    }
    private IEnumerable<NamedValue<bool>> GetNamedValues()
    {
        var yesNamedValue = new NamedValue<bool> { Name = "Yes", Value = true };
        var noNamedValue = new NamedValue<bool> { Name = "Yes", Value = false };
        yield return yesNamedValue;
        yield return noNamedValue;
    }
}

以下测试代码运行良好(断言通过):

public class Tester
{
    public Tester()
    {
        var source = new NamedValueSource<bool>();
        Debug.Assert(source.NamedValues[0].Name == "Yes");
    }
}

现在,这是有趣的部分。如果我尝试在 GetNamedValues() 中执行强制转换,代码将无法编译:

public class NamedValueSourceFail<TValue>
{
    public List<NamedValue<TValue>> NamedValues { get; set; }
    public NamedValueSourceFail()
    {
        NamedValues = GetNamedValues().ToList();
    }
    private IEnumerable<NamedValue<TValue>> GetNamedValues()
    {
        var yesNamedValue = new NamedValue<bool> { Name = "Yes", Value = true };
        var noNamedValue = new NamedValue<bool> { Name = "Yes", Value = false };
        yield return (NamedValue<TValue>)yesNamedValue; // ERROR: cannot convert type
        yield return (NamedValue<TValue>)noNamedValue; // ERROR: cannot convert type
    }
}

为什么NamedValueSource<TValue>NamedValueSourceFail<TValue>错误的同时进行编译?具体来说,为什么我能够使用 Linq 进行演员表,但不能使用好的 ol' parase?

编辑

如果从接受答案的评论线程中不完全清楚,我只需要先转换为object,然后我就可以投NamedValue<TValue>.这可能是 Linq Cast 方法在后台的工作方式。

为什么 C# 编译器允许使用 Linq 执行强制转换,但不允许使用括号执行转换

更新:这个问题是我2012年7月10日博客的主题; 谢谢你的好问题!


让我们大大简化您的复杂程序。

public static class X
{
    public static V Cast<V>(object o) { return (V)o; }
}
class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = X.Cast<C<U>>(new C<bool>());
    }
}

现在您的第二个版本,简化:

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = (C<U>)(new C<bool>());
    }
}

好的,现在让我们问一些问题。

为什么第二个程序在编译时失败?

因为对于任意U,没有从C<bool>C<U>的转换。编译器知道,唯一可能成功的方法就是U总是布尔值,因此这个程序几乎肯定是错误的!编译器假设U在某些时候会不是布尔值。

那么为什么第一个程序在编译时成功呢?

编译器不知道为了错误检测,名为"X.Cast"的方法应该被视为强制转换运算符!就编译器而言,Cast 方法是一种方法,它接受一个对象并为V提供的任何类型参数返回V。在编译 D 的 ctor 主体时,编译器根本不知道某些方法(可能甚至不在这个程序中)会尝试执行一个将失败的强制转换,除非U碰巧是布尔值。

编译器根本没有将第一个版本视为错误的基础,即使它肯定是一个非常错误的程序。 您必须等到运行时才能发现您的程序是错误的。

现在让我们制作程序的第三个版本:

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = (C<U>)(object)(new C<bool>());
    }
}

这在编译时成功,所以让我们问:

为什么这在编译时会成功?

原因与第一个在编译时成功的原因完全相同。当您插入强制转换时,您实际上表示您希望将新构造的C<bool>视为对象,因此对于此表达式的其余分析,该表达式被视为对象类型,而不是更具体的C<bool>类型。

那么,为什么在这种情况下对C<U>提出反对是合法的呢?或者就此而言,在第一种情况下V

将对象强制转换为V是合法的V因为可以是对象的类型、对象的基类型或对象实现的接口,因此编译器允许转换,因为它认为有很多方法可能会成功。

基本上,将object投射到可以转换为object的任何内容是合法的。例如,不能将object强制转换为指针类型,因为不能将指针类型强制转换为object。但其他一切都是公平的游戏。

通过首先将强制转换为object,您将信息从编译器的范围内删除;您是在说"忽略您知道这总是出于错误检测目的C<bool>的事实。

在第二个示例中,您尝试将NamedValue<bool>转换为NamedValue<TValue> - 这不起作用,因为转换必须对任何类型参数有效。 您无法将NamedValue<bool>转换为NamedValue<int>NamedValue<string>NamedValue<AnythingElseOtherThanBool>

一种解决方案是NamedValueSource<TValue>抽象及其GetNamedValues()方法,然后创建一个类BooleanNamedValueSource : NamedValueSource<bool>类以在测试中使用。

在 linq 情况下,转换不是由编译器完成的;转换发生在已编译的方法中。 编译器只知道它正在调用一个接受IEnumerable<bool>并返回IEnumerable<TValue>的方法。 该转换的细节对编译器完全不可见。