3

我有一个通用类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' parantheses?

编辑

如果从已接受答案的评论线程中不完全清楚,我只需object要先转换为,然后我将被允许转换为NamedValue<TValue>. 这可能是 LinqCast方法在幕后工作的方式。

4

2 回答 2

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>());
    }
}

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

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

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

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

因为没有从C<bool>toC<U>的任意转换U。编译器知道这可能成功的唯一方法是总是布尔,因此这个程序几乎肯定是错误的!编译器假定在某些时候这将是 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>());
    }
}

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

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

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

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

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

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

通过强制转换为objectfirst,您正在从编译器的权限中删除信息;您是在说“忽略您知道这始终C<bool>是为了错误检测的事实。

于 2012-04-06T20:49:08.200 回答
8

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

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

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

于 2012-04-06T20:27:32.507 回答