为什么此Queryable.Where调用会更改Queryable的Type参数? [英] Why Does This Queryable.Where Call Change the Queryable's Type Parameter?

查看:58
本文介绍了为什么此Queryable.Where调用会更改Queryable的Type参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到一种情况,其中IQueryable.Where<TSource>调用返回了IQueryable<TOther>,其中TOther != TSource.我整理了一些示例代码来重现它:

I ran into a situation where an IQueryable.Where<TSource> call was returning an IQueryable<TOther> where TOther != TSource. I put together some sample code to reproduce it:

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,但是返回的对象不再意识到它包含子类型,并认为它仅包含父类型.

Summary of Code Results

So, we store an IQueryable<ChildQueryElement> in an Object, then cast the object to IQueryable<ParentQueryElement>, where the child type inherits from the parent type. At this point the object stored in the Object variable still knows it is a collection of the child type. We then call Queryable.Where on it, but the object that is returned is no longer aware that it contains the child type, and thinks it only contains the parent type.

为什么会这样?除了跳过将其存储在对象中的步骤之外,还有什么方法可以避免这种情况?我之所以这样问,是因为我正在处理一个第三方API,该API要求我为其传递一个Object,并且我不想重写一堆第三方代码.

Why does this happen? Is there any way I can avoid this, other than skipping the step where it gets stored in an object? I ask this because I'm dealing with a third-party API that demands I pass it an Object, and I don't want to have to rewrite a bunch of third-party code.

从Jon Skeet得到一些建议后,我尝试了此示例代码,该示例代码使用动态变量进行复制.将Main的正文替换为以下内容:

After getting some advice from Jon Skeet, I tried this sample code, which uses a dynamic variable for copy. Replace the body of Main with the following:

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

不幸的是,输出保持不变.

Unfortunately, the output remains the same.

推荐答案

为什么会这样?

Why does this happen?

因为Where调用正在接收ParentQueryElement的类型参数作为TSource.它将基于TSource的结果作为新对象创建...因此您最终得到的东西知道"了ParentQueryElement而不是ChildQueryElement.

Because the Where call is receiving a type argument of ParentQueryElement as TSource. It creates a result based on TSource as a new object... so you end up with something which "knows" about ParentQueryElement instead of ChildQueryElement.

足够简单地演示这一点,而无需完全使用LINQ:

It's easy enough to demonstrate this without going into LINQ at all:

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

除了跳过将其存储在对象中的步骤之外,还有什么方法可以避免这种情况?

Is there any way I can avoid this, other than skipping the step where it gets stored in an object?

好吧,您可以动态调用Where,这时将在执行时推断出type参数:

Well, you could call Where dynamically, at which point the type argument will be inferred at execution time instead:

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>.我不清楚这是否会对您造成问题.

Note that the expression tree type needs to be Func<ChildQueryElement, bool> as well... it's not clear to me whether that would be a problem for you.

这篇关于为什么此Queryable.Where调用会更改Queryable的Type参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆