猿问

为什么编译器不能解析结果类型?

我有两个IEnumerable不同类型的 s,它们都派生自一个公共基类。现在我尝试联合枚举来获得基类的枚举。我必须明确地将其中一个枚举类型转换为基类才能使其正常工作。


我猜想编译器会自动为生成的可枚举选择最接近的通用基本类型,但事实并非如此。


这是该行为的一个示例:


namespace ConsoleApp1

{

    public class BaseClass { }

    public class DerivedClass1 : BaseClass { }

    public class DerivedClass2 : BaseClass { }


    class Program

    {

        static void Main(string[] args)

        {

            List<DerivedClass1> list1 = new List<DerivedClass1>();

            List<DerivedClass2> list2 = new List<DerivedClass2>();

            var a = list1.Union(list2); // Compiler Error

            IEnumerable<BaseClass> b = list1.Union(list2); // Compiler Error

            var c = list1.Cast<BaseClass>().Union(list2); // This works

            var d = list1.Union(list2.Cast<BaseClass>()); // This works

            var e = list1.Cast<BaseClass>().Union(list2.Cast<BaseClass>()); // This works, but ReSharper wants to remove one of the casts

        }

    }

}

var c似乎很容易解释,因为第一个 enumerable 现在是 type BaseClass,所以与第二个列表的联合,其中包含从 BaseClass 派生的元素也很容易理解。


var d对我来说不是那么容易理解,因为我们从一个可枚举的元素开始DerivedClass1并将其与BaseClass元素联合。我很惊讶这行得通。是因为联合操作是一种交换操作,所以它必须像c也一样有效吗?


RISEBY
浏览 135回答 3
3回答

江户川乱折腾

值得记住的是,这Union是一种扩展方法。这是您正在调用的方法签名:public static IEnumerable<TSource> Union<TSource> (&nbsp; &nbsp; this IEnumerable<TSource> first,&nbsp;&nbsp; &nbsp; IEnumerable<TSource> second);所以你的电话是有效的:var a = Enumerable.Union(list1, list2);IEnumerable<BaseClass> b = Enumerable.Union(list1, list2);&nbsp;var c = Enumerable.Union(list1.Cast<BaseClass>(), list2);var d = Enumerable.Union(list1, list2.Cast<BaseClass>());var e = Enumerable.Union(list1.Cast<BaseClass>(), list2.Cast<BaseClass>());这些调用中涉及的参数类型是:a: List<DerivedClass1>, List<DerivedClass2>b: List<DerivedClass1>, List<DerivedClass2> // Variable being assigned to doesn't matterc: IEnumerable<BaseClass>, List<DerivedClass2>d: List<DerivedClass1>, IEnumerable<BaseClass>e: IEnumerable<BaseClass>, IEnumerable<BaseClass>这里显然分为三类:a并且b是相同的。我们稍后再看。c并且d是彼此的镜像。this请注意,就涉及类型推断而言,使用哪个作为参数并不重要。以后多...e很简单:以非常明显的方式T推断BaseClass现在a并且b不工作,因为BaseClass从不作为候选类型存在。根据我对类型推断算法的记忆(它确实非常复杂),当任何参数类型中都不存在该类型时,该类型将永远不会被推断为类型参数。因此,虽然BaseClass和object都是有效的显式泛型类型参数,但它们都不会被推断出来。c并d决心T成为BaseClass,因为有推论要求从IEnumerable<BaseClass>to转换IEnumerable<T>,并且从List<DerivedClass2>or List<DerivedClass1>(分别为cand d) to 转换IEnumerable<T>。这仅适用于T=BaseClass所考虑的类型,所以这就是推断的。

慕姐4208626

为什么编译器不能解析结果类型?这个问题是基于错误的信念;编译器无法找出通用类型。编译器可以很容易地弄清楚这一点,它只是选择不这样做,这是有充分理由的。当编译器对此进行推理时,它将始终使用表达式中涉及的类型。如果不是,那么它应该停在哪里?一切都可以简化IEnumerable<object>并使其工作,但这可能会掩盖意外行为并且可能是错误://ee is implicitly typed as IEnumerable<object>var ee = someEnumerableOfString.Union(someEnumerableOfFoo);操作员也会发生类似的事情?。编译器不会去寻找一个共同的祖先,无论是共同的基类型、接口还是简单的对象。它是一个很好的功能,因为它并不总是清楚你想要什么共性。想象一下:class A: IBar { }class B: A, IFooclass C: A, IFoovar aa = someEnumerableOfB.Union(someEnumerableOfC);为什么应该aa是 type IEnumerable<A>?为什么不IEnumerable<IFoo>呢?为什么不IEnumerable<IBar>呢?它可以永远持续下去......在以下假设的法律声明中,另一个需要考虑的重要因素是,这可能会导致混淆:IEnumerable<A> aa = someEnumerableOfB.Union(someEnumerableOfC);有人可能会争辩说,您正在明确指定您想要与IEnumerable<A>.但这不是编译器推断类型的方式;编译器不会尝试根据赋值左侧的类型信息来判断右侧的类型。它的工作方式是尝试找出右侧的类型,如果成功,那么它将查看其是否可分配给左侧。您得到的错误是因为它在第一步中失败了。

Qyouu

它不能将 DerivedClass2 与 DerivedClass1 联合,它们共享一个基类,但无论如何它们都不是同一类型var&nbsp;c&nbsp;=&nbsp;list1.Cast<BaseClass>().Union(list2);&nbsp;//&nbsp;This&nbsp;works这将起作用,因为现在联合在 BaseClass 和 DerivedClass2 之间,并且 DerivedClass2 可以转换为 BaseClass,因为 baseClass.GetType().IsAssigneableFrom(derivedClass2)
随时随地看视频慕课网APP
我要回答