我遇到了一个IQueryable.Where<TSource>
来电正在IQueryable<TOther>
来回TOther != TSource
的情况。我整理了一些示例代码来重现它:
using System;
using System.Collections.Generic;
using System.Linq;
namespace IQueryableWhereTypeChange {
class Program {
static void Main( string[] args ) {
var ints = new List<ChildQueryElement>();
for( int i = 0; i < 10; i++ ) {
ints.Add( new ChildQueryElement() { Num = i, Value = i.ToString() } );
}
IQueryable<ChildQueryElement> theIQ = ints.AsQueryable();
Object theObj = theIQ;
Type theObjElementType = ( (IQueryable<ParentQueryElement>) theObj ).ElementType;
Type theObjGenericType = ( (IQueryable<ParentQueryElement>) theObj ).GetType().GetGenericArguments()[ 0 ];
var iQ = ( (IQueryable<ParentQueryElement>) theObj );
var copy = iQ;
Type copyType1 = copy.GetType().GetGenericArguments()[ 0 ];
Type elementType1 = copy.ElementType;
copy = copy.Where( qe1 => true );
Type copyType2 = copy.GetType().GetGenericArguments()[ 0 ];
Type elementType2 = copy.ElementType;
Console.WriteLine( "theObjElementType : " + theObjElementType.ToString() );
Console.WriteLine( "theObjGenericType : " + theObjGenericType.ToString() );
Console.WriteLine( "copyType1 : " + copyType1.ToString() );
Console.WriteLine( "elementType1 : " + elementType1.ToString() );
Console.WriteLine( "copyType2 : " + copyType2.ToString() );
Console.WriteLine( "elementType2 : " + elementType2.ToString() );
}
}
public class ParentQueryElement {
public int Num { get; set; }
}
public class ChildQueryElement : ParentQueryElement {
public string Value { get; set; }
}
}
该程序的输出是:
theObjElementType : IQueryableWhereTypeChange.ChildQueryElement
theObjGenericType : IQueryableWhereTypeChange.ChildQueryElement
copyType1 : IQueryableWhereTypeChange.ChildQueryElement
elementType1 : IQueryableWhereTypeChange.ChildQueryElement
copyType2 : IQueryableWhereTypeChange.ParentQueryElement
elementType2 : IQueryableWhereTypeChange.ParentQueryElement
因此,我们在IQueryable<ChildQueryElement>
中存储Object
,然后将对象强制转换为IQueryable<ParentQueryElement>
,其中子类型继承父类型。此时,存储在Object
变量中的对象仍然知道它是子类型的集合。然后我们在其上调用Queryable.Where
,但返回的对象不再知道它包含子类型,并认为它只包含父类型。
为什么会这样?除了跳过存储在对象中的步骤之外,有什么方法可以避免这种情况吗?我问这个是因为我正在处理第三方API,要求我传递Object
,我不想重写一堆第三方代码。
在从Jon Skeet获得一些建议后,我尝试了这个示例代码,它使用动态变量进行复制。用以下内容替换Main
的正文:
var ints = new List<ChildQueryElement>();
for( int i = 0; i < 10; i++ ) {
ints.Add( new ChildQueryElement() { Num = i, Value = i.ToString() } );
}
IQueryable<ChildQueryElement> theIQ = ints.AsQueryable();
Object theObj = theIQ;
Type theObjElementType = ( (IQueryable<ParentQueryElement>) theObj ).ElementType;
Type theObjGenericType = ( (IQueryable<ParentQueryElement>) theObj ).GetType().GetGenericArguments()[ 0 ];
var iQ = ( (IQueryable<ParentQueryElement>) theObj );
dynamic copy = iQ;
Type copyType1 = copy.GetType().GetGenericArguments()[ 0 ];
Type elementType1 = ((IQueryable)copy).ElementType;
Expression<Func<ParentQueryElement, bool>> del = qe => true;
copy = Queryable.Where( copy, del );
Type copyType2 = copy.GetType().GetGenericArguments()[ 0 ];
Type elementType2 = ((IQueryable)copy).ElementType;
Console.WriteLine( "theObjElementType : " + theObjElementType.ToString() );
Console.WriteLine( "theObjGenericType : " + theObjGenericType.ToString() );
Console.WriteLine( "copyType1 : " + copyType1.ToString() );
Console.WriteLine( "elementType1 : " + elementType1.ToString() );
Console.WriteLine( "copyType2 : " + copyType2.ToString() );
Console.WriteLine( "elementType2 : " + elementType2.ToString() );
不幸的是,输出保持不变。
答案 0 :(得分:2)
为什么会这样?
因为Where
调用正在接收ParentQueryElement
类型参数TSource
。它会根据TSource
作为新对象创建结果...因此您最终会得到“了解”ParentQueryElement
而不是ChildQueryElement
的内容。
很容易在没有进入LINQ的情况下证明这一点:
using System;
public interface IWrapper<out T>
{
T Value { get; }
}
public class Wrapper<T> : IWrapper<T>
{
private readonly T value;
public Wrapper(T value)
{
this.value = value;
}
public T Value { get { return value; } }
}
class Program
{
static void Main(string[] args)
{
IWrapper<string> original = new Wrapper<string>("foo");
IWrapper<object> original2 = original;
IWrapper<object> rewrapped = Rewrap(original2);
Console.WriteLine(original2.GetType()); // Wrapper<string>
Console.WriteLine(rewrapped.GetType()); // Wrapper<object>
}
static IWrapper<T> Rewrap<T>(IWrapper<T> wrapper)
{
return new Wrapper<T>(wrapper.Value);
}
}
除了跳过存储在对象中的步骤之外,有什么方法可以避免这种情况吗?
好吧,你可以动态调用Where
,此时类型参数将在执行时推断:
dynamic copy = ...;
Expression<Func<ChildQueryElement, bool>> filter = qe1 => true;
// Can't call an extension method "on" dynamic; call it statically instead
copy = Queryable.Where(copy, filter);
请注意,表达式树类型也必须是Func<ChildQueryElement, bool>
...我不清楚这对您来说是否会有问题。
答案 1 :(得分:0)
如果您确定查询中的元素将是ChildQueryElement
类型,那么您可以简单地使用Cast
方法吗?
copy = copy.Where(qe1 => true); // IQueryable<ParentQueryElement>
var copyCasted = copy.Cast<ChildQueryElement>(); // IQueryable<ChildQueryElement>