【问题标题】:Linq - Casting IQueryable to IList returns null - WHY?Linq - 将 IQueryable 转换为 IList 返回 null - 为什么?
【发布时间】:2009-01-30 02:34:13
【问题描述】:

我必须在这里遗漏一些明显的东西。我不明白为什么这种 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; }
}

【问题讨论】:

  • 当您删除安全强制转换 ("as IList") 时会发生什么?我认为您可以执行 .ToList() 并将其粘贴在您的 IList 变量中。
  • 我也是这么想的,但是没用。

标签: linq generics interface


【解决方案1】:

这里的问题是协方差之一。

首先,您的示例有点过于复杂。我去掉了一些绒毛。此外,我还添加了一些诊断程序来说明问题。

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&lt;C&gt; 强制转换为 List&lt;I&gt;,这在 C# 3.0 中不起作用。

在 C# 4.0 中,您应该能够做到这一点,这要归功于泛型接口上类型参数的新协变和反变。

此外,您最初的问题是关于 IQueryable 的问题,但这与此处无关:您提供的查询表达式创建的是 IEnumerable&lt;string&gt; 而不是 IQueryable&lt;string&gt;。

编辑:我想指出,您使用 as 运算符的“强制转换”在技术上不是强制转换,而是“类型转换”。如果你使用了演员表,你会得到一个包含有用信息的异常。如果我改为:

    var list = (IList<I>)query.ToList();

我收到了一个InvalidCastException:

附加信息:无法转换类型为“System.Collections.Generic.List1[C]' to type 'System.Collections.Generic.IList1[I]”的对象。

【讨论】:

  • 请注意,在 C# 4.0 中,协变/逆变仅适用于接口和委托,因此在 C# 4.0 中将 List 转换为 List 也不起作用,这是正确的。
  • 我不确定我是否理解这个答案。类型转换/强制转换是否应该在 C# 4.0 中工作? (注意:即使我使用的是 C# 4.0,也会收到 InvalidCastException。)
  • (我已经离开 C# 有一段时间了,所以我有点模糊。) C# 4.0 为接口类型参数添加了协变和逆变。 NDP 为协方差和反方差注释了一些接口。例如,在我的 Beta 2 版本中,我看到 IEnumerable 现在是 out T,但 IList 不是。所以在上面的代码中我可以写query.ToList() as IEnumerable&lt;I&gt;,它可以工作(非空)。
【解决方案2】:

试试这个:

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 扩展方法。例如:

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

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

我建议前者。

【讨论】:

  • 我对方法 1 中的演员阵容感到惊讶。每次我尝试这样的事情时,我都会遇到问题中描述的相同问题。我找到的唯一解决方案是 Cast() 扩展方法,可以在 .ToList() 之前或之后调用。
  • 我在发布之前进行了测试。转换在方法 1 中起作用,因为选择部分可以采用任何有效的 C# 构造表达式。
猜你喜欢
  • 2020-04-23
  • 1970-01-01
  • 2013-10-30
  • 2015-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-02
相关资源
最近更新 更多