繁体   English   中英

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

[英]Why does the C# compiler allow a cast to be performed with Linq but not with parentheses?

我有一个通用的 class NamedValue<TValue>

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

我有第二个通用 class, 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'parantheses?

编辑

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

更新:这个问题是我 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始终是 bool,因此这个程序几乎肯定是错误的! 编译器假设U在某些时候会是除 bool 之外的其他东西。

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

编译器不知道出于错误检测的目的,应将名为“X.Cast”的方法视为强制转换运算符,就编译器而言, Cast方法是一种接受 object 并返回一个VV提供的任何类型参数。 在编译 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>视为 object,因此对于此表达式分析的 rest,该表达式被视为类型 object,而不是更具体的类型C<bool>

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

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

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

通过首先转换为object ,您正在从编译器的权限中删除信息 你是说“忽略你知道这总是C<bool>用于错误检测的事实。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM