代码之家  ›  专栏  ›  技术社区  ›  Colin Burnett

C#编译器的泛型、继承和失败的方法解析

  •  6
  • Colin Burnett  · 技术社区  · 16 年前

    今天我遇到了一个让我困惑的编译问题。考虑一下这两个容器类。

    public class BaseContainer<T> : IEnumerable<T>
    {
        public void DoStuff(T item) { throw new NotImplementedException(); }
    
        public IEnumerator<T> GetEnumerator() { }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { }
    }
    public class Container<T> : BaseContainer<T>
    {
        public void DoStuff(IEnumerable<T> collection) { }
    
        public void DoStuff <Tother>(IEnumerable<Tother> collection)
            where Tother: T
        {
        }
    }
    

    DoStuff(T item) 后者使其过载 DoStuff <Tother>(IEnumerable<Tother>) covariance/contravariance C#(直到我听到4)。

    此代码

    Container<string> c = new Container<string>();
    c.DoStuff("Hello World");
    

    遇到一个相当奇怪的编译错误。注意缺少 <char> 从方法调用。

    从本质上讲,编译器试图干扰我对 DoStuff(string) Container.DoStuff<char>(IEnumerable<char>) string 实现 IEnumerable<char> ,而不是使用 BaseContainer.DoStuff(string) .

    我发现使此编译的唯一方法是添加 DoStuff(T) 到派生类

    public class Container<T> : BaseContainer<T>
    {
        public new void DoStuff(T item) { base.DoStuff(item); }
    
        public void DoStuff(IEnumerable<T> collection) { }
    
        public void DoStuff <Tother>(IEnumerable<Tother> collection)
            where Tother: T
        {
        }
    }
    

    为什么编译器试图将字符串阻塞为 IEnumerable<char> 当1)它知道它不能(考虑到存在编译错误)并且2)它在基类中有一个编译良好的方法时? 我对C#中的泛型或虚方法有什么误解吗?除了添加 new DoStuff(T item) Container ?

    6 回复  |  直到 8 年前
        1
  •  3
  •   Eoin Campbell    16 年前

    编辑

    好吧…我想我现在看到你的困惑了。您会期望DoStuff(string)将参数保持为字符串,并首先遍历基类方法列表以寻找合适的签名,如果失败,则尝试将参数转换为其他类型。

    但事情恰恰相反。..相反 Container.DoStuff(string) goed,meh“那里有一个符合要求的基类方法,但我将转换为IEnumerable,并对当前类中可用的方法产生心脏病发作。。。

    嗯……我相信Jon或Marc在这一点上能够插话,具体介绍一下C#规范中涵盖这一特殊情况的段落

    原创

    这两种方法都需要一个IEnumerable集合

    好的,我有一个字符串,两种方法都有 期待A IEnumerable<T> ,所以我会 将此字符串转换为 IEnumerable<char> …完成

    好的,检查第一种方法。.. 嗯……这门课是 Container<string> 但我有一个 IEnumerable<char> 所以这是不对的。

    检查第二种方法,嗯……我 有一个 IEnumerable<char> 但是char 没有实现字符串,所以 也不对。

    编译错

    那么,解决方案是什么,这完全取决于你想要实现什么。..以下两种情况都是有效的,本质上,你的类型用法在你的化身中是不正确的。

            Container<char> c1 = new Container<char>();
            c1.DoStuff("Hello World");
    
            Container<string> c2 = new Container<string>();
            c2.DoStuff(new List<string>() { "Hello", "World" });
    
        2
  •  3
  •   dss539    16 年前

    正如Eric Lippert解释的那样,编译器选择 DoStuff<Tother>(IEnumerable<Tother>) where Tother : T {} 方法,因为它在检查约束之前选择方法。因为string可以 IEnumerable<> ,编译器将其与该子类方法匹配。 这 编译器工作正常 如C#规范中所述。

    通过将DoStuff实现为 extension method . 检查扩展方法 之后 基类方法,因此它不会尝试匹配 string 反对 DoStuff s IEnumerable<Tother> 直到 之后 它试图与之匹敌 DoStuff<T> .

    以下代码演示了所需的方法解析顺序、协方差和继承。请将其复制/粘贴到新项目中。

    缺点 我能想到的是,到目前为止,你不能使用 base 在覆盖方法中,但我认为有一些方法可以解决这个问题(问你是否感兴趣)。

    using System;
    using System.Collections.Generic;
    
    namespace MethodResolutionExploit
    {
        public class BaseContainer<T> : IEnumerable<T>
        {
            public void DoStuff(T item) { Console.WriteLine("\tbase"); }
            public IEnumerator<T> GetEnumerator() { return null; }
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return null; }
        }        
        public class Container<T> : BaseContainer<T> { }
        public class ContainerChild<T> : Container<T> { }
        public class ContainerChildWithOverride<T> : Container<T> { }
        public static class ContainerExtension
        {
            public static void DoStuff<T, Tother>(this Container<T> container, IEnumerable<Tother> collection) where Tother : T
            {
                Console.WriteLine("\tContainer.DoStuff<Tother>()");
            }
            public static void DoStuff<T, Tother>(this ContainerChildWithOverride<T> container, IEnumerable<Tother> collection) where Tother : T
            {
                Console.WriteLine("\tContainerChildWithOverride.DoStuff<Tother>()");
            }
        }
    
        class someBase { }
        class someChild : someBase { }
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("BaseContainer:");
                var baseContainer = new BaseContainer<string>();
                baseContainer.DoStuff("");
    
                Console.WriteLine("Container:");
                var container = new Container<string>();
                container.DoStuff("");
                container.DoStuff(new List<string>());
    
                Console.WriteLine("ContainerChild:");
                var child = new ContainerChild<string>();
                child.DoStuff("");
                child.DoStuff(new List<string>());
    
                Console.WriteLine("ContainerChildWithOverride:");
                var childWithOverride = new ContainerChildWithOverride<string>();
                childWithOverride.DoStuff("");
                childWithOverride.DoStuff(new List<string>());
    
                //note covariance
                Console.WriteLine("Covariance Example:");
                var covariantExample = new Container<someBase>();
                var covariantParameter = new Container<someChild>();
                covariantExample.DoStuff(covariantParameter);
    
                // this won't work though :(
                // var covariantExample = new Container<Container<someBase>>();
                // var covariantParameter = new Container<Container<someChild>>();
                // covariantExample.DoStuff(covariantParameter);
    
                Console.ReadKey();
            }
        }
    }
    

    输出如下:

    BaseContainer:
            base
    Container:
            base
            Container.DoStuff<Tother>()
    ContainerChild:
            base
            Container.DoStuff<Tother>()
    ContainerChildWithOverride:
            base
            ContainerChildWithOverride.DoStuff<Tother>()
    Covariance Example:
            Container.DoStuff<Tother>()
    

    你能看出这项工作有什么问题吗?

        3
  •  2
  •   BFree    16 年前

    我的猜测是,它首先在派生类中查找以解析方法调用(因为你的对象是派生类型的对象)。如果,并且只有当它不能,它才会继续查看基类方法来解决它。在你的情况下,因为它可以使用

    DoStuff <Tother>(IEnumerable<Tother> collection)
    

        4
  •  2
  •   Philippe Leybaert    16 年前

    编译器将尝试将参数与IEnumerable匹配 <T> String类型实现了IEnumerable <char> ,所以它假设T是“char”。

    之后,编译器检查另一个条件“where OtherT:T”,但该条件不满足。因此出现了编译器错误。

        5
  •  1
  •   n8wrl    16 年前

    我认为这与char是一种值类型而string是一种引用类型的事实有关。看起来你在定义

    TOther : T
    

    char不是从string派生出来的。

        6
  •  0
  •   justin justin    16 年前

    我不太清楚你想实现什么,是什么阻止了你只使用两种方法, DoStuff(T item) and DoStuff(IEnumerable<T> collection)?