为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

C#
RISEBY 2022-06-12 10:42:57
我有两个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也一样有效吗?
查看完整描述

3 回答

?
江户川乱折腾

TA贡献1851条经验 获得超5个赞

值得记住的是,这Union是一种扩展方法。这是您正在调用的方法签名:


public static IEnumerable<TSource> Union<TSource> (

    this IEnumerable<TSource> first, 

    IEnumerable<TSource> second);

所以你的电话是有效的:


var a = Enumerable.Union(list1, list2);

IEnumerable<BaseClass> b = Enumerable.Union(list1, list2); 

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 matter

c: 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所考虑的类型,所以这就是推断的。


查看完整回答
反对 回复 2022-06-12
?
慕姐4208626

TA贡献1852条经验 获得超7个赞

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


这个问题是基于错误的信念;编译器无法找出通用类型。编译器可以很容易地弄清楚这一点,它只是选择不这样做,这是有充分理由的。


当编译器对此进行推理时,它将始终使用表达式中涉及的类型。如果不是,那么它应该停在哪里?一切都可以简化IEnumerable<object>并使其工作,但这可能会掩盖意外行为并且可能是错误:


//ee is implicitly typed as IEnumerable<object>

var ee = someEnumerableOfString.Union(someEnumerableOfFoo);

操作员也会发生类似的事情?。编译器不会去寻找一个共同的祖先,无论是共同的基类型、接口还是简单的对象。


它是一个很好的功能,因为它并不总是清楚你想要什么共性。想象一下:


class A: IBar { }

class B: A, IFoo

class C: A, IFoo


var aa = someEnumerableOfB.Union(someEnumerableOfC);

为什么应该aa是 type IEnumerable<A>?为什么不IEnumerable<IFoo>呢?为什么不IEnumerable<IBar>呢?它可以永远持续下去......


在以下假设的法律声明中,另一个需要考虑的重要因素是,这可能会导致混淆:


IEnumerable<A> aa = someEnumerableOfB.Union(someEnumerableOfC);

有人可能会争辩说,您正在明确指定您想要与IEnumerable<A>.


但这不是编译器推断类型的方式;编译器不会尝试根据赋值左侧的类型信息来判断右侧的类型。它的工作方式是尝试找出右侧的类型,如果成功,那么它将查看其是否可分配给左侧。您得到的错误是因为它在第一步中失败了。


查看完整回答
反对 回复 2022-06-12
?
Qyouu

TA贡献1786条经验 获得超11个赞

它不能将 DerivedClass2 与 DerivedClass1 联合,它们共享一个基类,但无论如何它们都不是同一类型

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

这将起作用,因为现在联合在 BaseClass 和 DerivedClass2 之间,并且 DerivedClass2 可以转换为 BaseClass,因为 baseClass.GetType().IsAssigneableFrom(derivedClass2)


查看完整回答
反对 回复 2022-06-12
  • 3 回答
  • 0 关注
  • 134 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信