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

时间:2009-01-30 02:34:14

标签: linq generics interface

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

IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;

下面是运行此代码的完整代码。这是我需要弥合的知识差距。我已经尝试过各种各样的演员阵容来让它发挥作用。我没有异常,只是一个空。值得注意的是,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; }
}

2 个答案:

答案 0 :(得分: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; }
}

该程序的输出是:

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

我得到了InvalidCastException

Additional information: Unable to cast object of type 'System.Collections.Generic.List1[C]' to type 'System.Collections.Generic.IList1[I]'.

答案 1 :(得分:5)

试试这个:

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

你的问题在这一行:

IList<IMyDataInterface> list = query.ToList() as IList<IMyDataInterface>;

这也可以写成:

List<MyDataClass> tmp = query.ToList();
IList<IMyDataInterface> list = tmp as IList<IMyDataInterface>;

不幸的是,在C#中,as运算符无法以您希望的方式运行。 as运算符只是将列表对象强制转换为不同类型的列表;它不会尝试通过列表并投射每个项目。要将列表转换为其他内容,您需要在其上调用Cast扩展方法。 E.g:

IList<IMyDataInterface> list = query.ToList().Cast<IMyDataInterface>();

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

我建议前者。