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

dev*_*xer 3 c# linq generics casting

我有一个通用类NamedValue<TValue>:

public class NamedValue<TValue>
{
    public string Name { get; set; }
    public TValue Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我有第二个泛型类,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

以下测试代码完美运行(断言传递):

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

现在,这是有趣的部分.如果我尝试在其中执行转换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
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么NamedValueSource<TValue>编译时NamedValueSourceFail<TValue>出错?具体来说,为什么我能够使用Linq执行演员表而不是使用良好的ol'parantheses?

编辑

如果从接受的答案的评论帖中不完全清楚,我只需要转换为object第一个,然后我将被允许转换为NamedValue<TValue>.这可能是Linq Cast方法在幕后工作的方式.

Eri*_*ert 12

更新:这个问题是我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>());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = (C<U>)(new C<bool>());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

因为没有从转换C<bool>C<U>任意U.编译器知道这可能成功的唯一方法是总是 bool,因此这个程序几乎肯定是错误的!编译器假定在某些时候它将不是bool.UU

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

为了进行错误检测,编译器不知道名为"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>());
    }
}
Run Code Online (Sandbox Code Playgroud)

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

为什么这在编译时成功?

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

那么为什么C<U>在这种情况下将对象转换为合法呢?或者就此而言,V在第一种情况下?

将对象转换为合法是V因为V可能是对象的类型,对象的基本类型或对象实现的接口,因此编译器允许转换,因为它表明有很多方法可能成功.

基本上,投射object你可以转换成的任何东西object是合法的.object例如,您无法转换为指针类型,因为无法转换为指针类型object.但其他一切都是公平的游戏.

通过将强制转换设置为object第一个,您将从编译器的权限中删除信息 ; 你说的是"忽略这样一个事实,即你知道这总是C<bool>出于错误检测的目的.


pho*_*oog 8

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

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

在linq的情况下,编译器没有完成转换; 强制转换发生在已经编译的方法中.所有编译器都知道它正在调用一个获取IEnumerable<bool>并返回的方法IEnumerable<TValue>.转换的细节对编译器完全不可见.