Linq - 为IList转换IQueryable返回null - 为什么?

jle*_*bke 2 linq generics interface

我必须在这里遗漏一些明显的东西.我不明白为什么这个linq查询结果的转换返回null而不是我正在请求的类型列表.

IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;
Run Code Online (Sandbox Code Playgroud)

运行它的完整代码如下.这是我需要弥合的知识差距.我已经尝试过各种各样的演员阵容来让它发挥作用.我没有异常,只是一个空.值得注意的是,Linq查询将其结果选择为我的自定义"MyDataClass"的实例,该实现IMyDataInterface

class Program
{
    static void Main(string[] args)
    {
        IMyFunctionalInterface myObject = new MyClass();


        //myObject.Get() returns null for some reason...
        IList<IMyDataInterface> list = myObject.Get();

        Debug.Assert(list != null, "Cast List is null");
    }
}

public interface IMyFunctionalInterface
{
    IList<IMyDataInterface> Get();
}

public class MyClass : IMyFunctionalInterface
{
    public IList<IMyDataInterface> Get()
    {
        string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };

        var query = from n in names
                    where n.Contains("a")
                    select new MyDataClass
                    {
                        Name = n.ToString()
                    };

        //There IS data in the query result
        Debug.Assert(query != null, "result is null");
        //but the cast here makes it return null
        IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;

        return list;
    }

}
public interface IMyDataInterface
{
    string Name { get; set; }
}

public class MyDataClass : IMyDataInterface
{
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

Jay*_*uzi 13

这里的问题是协方差问题.

首先,你的例子有点太复杂了.我已经删除了一些绒毛.此外,我添加了一些解决问题的诊断.

class Program
{
    static void Main(string[] args)
    {
        var names = new[] { "Tom", "Dick", "Harry", "Mary", "Jay" };

        var query = from n in names
                    select new C
                    {
                        S = n
                    };

        //There IS data in the query result
        Debug.Assert(query != null, "result is null");

        //but the conversion here makes it return null
        var list = query.ToList() as IList<I>;
        Console.WriteLine(query.ToList().GetType());

        // this assert fires.
        Debug.Assert(list != null, "Cast List is null");
    }
}

interface I
{
    string S { get; set; }
}

class C : I
{
    public string S { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

该程序的输出是:

System.Collections.Generic.List`1 [C]

请注意,我们正在尝试强制转换List<C>List<I>在C#3.0中不起作用的内容.

在C#4.0中,你应该能够做到这一点,这要归功于通用接口上类型参数的新的共同和反差.

此外,您的原始问题询问IQueryable但在此处不相关:您提供的查询表达式创建的IEnumerable<string>不是IQueryable<string>.

编辑:我想指出,使用as运算符的"强制转换"在技术上不是强制转换,而是"类型转换".如果您使用了演员表,那么您将获得有用信息的例外.如果我改为:

    var list = (IList<I>)query.ToList();
Run Code Online (Sandbox Code Playgroud)

我得到一个InvalidCastException:

附加信息:无法转换'System.Collections.Generic.List 1[C]' to type 'System.Collections.Generic.IList1 [I]' 类型的对象.

  • 请注意,在C#4.0中,协方差/逆变仅适用于接口和委托,因此C#4.0中List <C>到List <I>的强制转换也不起作用,这是正确的. (2认同)
  • (我已经离开C#一段时间了,所以我有点模糊.)C#4.0增加了接口类型参数的共同和反差.NDP注释了一些接口,用于协方差和反方差.例如,在我的Beta 2版本中,我看到`IEnumerable`现在是'out T`,但`IList`不是.所以在上面的代码中我可以将`query.ToList()写成IEnumerable <I>`并且它可以工作(非null). (2认同)

Ran*_*pho 5

试试这个:

var query = from n in names
            where n.Contains("a")
            select new MyDataClass
            {
              Name = n.ToString()
            } as IMyDataInterface;
Run Code Online (Sandbox Code Playgroud)

你的问题在于这一行:

IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;
Run Code Online (Sandbox Code Playgroud)

这也可以写成:

List<MyDataClass> tmp = query.ToList();
IList<IMyDataInterface> list = tmp as IList<IMyDataInterface>;
Run Code Online (Sandbox Code Playgroud)

不幸的是,在C#中,as运算符不能按照您希望的方式工作.的as操作者只需连铸列表对象作为不同类型的列表; 它不会尝试通过列表并投射每个项目.要将列表转换为其他内容,您需要在其上调用Cast扩展方法.例如:

IList<IMyDataInterface> list = query.ToList().Cast<IMyDataInterface>();
Run Code Online (Sandbox Code Playgroud)

因此,您的选项是:将查询中的任何项目作为您想要的界面(我的第一个示例)投射,或者在执行查询后投射整个列表(我的第二个示例).

我建议前者.