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

C#编译器的泛型、继承和失败的方法解析,c#,generics,compilation,C#,Generics,Compilation,今天我遇到了一个令我困惑的汇编问题。考虑这两个容器类。 public class BaseContainer<T> : IEnumerable<T> { public void DoStuff(T item) { throw new NotImplementedException(); } public IEnumerator<T> GetEnumerator() { } System.Collections.IEnumerator

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

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
    {
    }
}
遇到一个相当奇怪的编译错误。注意方法调用中没有

类型“char”不能用作泛型类型或方法“Container.DoStuff(System.Collections.generic.IEnumerable)”中的类型参数“Tother”。没有从“char”到“string”的装箱转换

实际上,编译器试图将我对
DoStuff(string)
的调用阻塞到
Container.DoStuff(IEnumerable)
中,因为
string
实现了
IEnumerable
,而不是使用
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
    {
    }
}
公共类容器:BaseContainer
{
public new void DoStuff(T项){base.DoStuff(项);}
公共void DoStuff(IEnumerable集合){}
公共无效数据集(IEnumerable集合)
托特在哪里
{
}
}

当1)它知道不能(考虑到存在编译错误)和2)它在基类中有一个编译良好的方法时,为什么编译器试图将字符串阻塞为
IEnumerable
?我是否误解了C#中的泛型或虚拟方法?除了在
容器中添加
新的DoStuff(T项)
之外,还有其他修复方法吗?

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

TOther : T

char不是从字符串派生的。

编译器将尝试将参数与IEnumerable
匹配。字符串类型实现IEnumerable
,因此它假定T是“char”


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

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

DoStuff <Tother>(IEnumerable<Tother> collection)
DoStuff(IEnumerable集合)
过载,它试图把它塞进那个里。因此,就参数而言,它可以解决这个问题,但在约束上遇到了障碍。在这一点上,它已经解决了您的过载问题,因此它不再进一步查看,而是抛出了一个错误。有意义吗?

编辑

好的。。。我想我现在明白你的困惑了。您可能希望DoStuff(string)将参数保持为字符串,并首先遍历基类方法列表以查找合适的签名,如果失败,则返回尝试将参数强制转换为其他类型

但事情发生在另一个方向。。。取而代之的是
Container.DoStuff(string)
go,meh“这里有一个适合的基类方法,但我将转换为IEnumerable,并对当前类中的可用内容感到心脏病发作

嗯……我相信Jon或Marc在这一点上能够与涉及这一特殊情况的特定C#Spec段落相呼应

原创

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

您正在传递单个字符串

编译器正在获取该字符串并运行

好的,我有一个字符串,两种方法都有 需要一个
IEnumerable
,因此我将 把这根绳子变成一根绳子
IEnumerable
…完成

对,检查第一种方法。。。 嗯…这个班是一个
容器
但我有一个
IEnumerable
所以这是不对的

检查第二种方法,嗯……I 有一个
IEnumerable
but char 不实现字符串,因此 也不对

编译器错误

因此,修复方法是什么,完全取决于您试图实现的目标……以下两项都是有效的,本质上,您的类型使用在您的化身中是不正确的

        Container<char> c1 = new Container<char>();
        c1.DoStuff("Hello World");

        Container<string> c2 = new Container<string>();
        c2.DoStuff(new List<string>() { "Hello", "World" });
Container c1=新容器();
c1.多斯塔夫(“你好世界”);
容器c2=新容器();
c2.DoStuff(新列表(){“你好”,“世界”});

正如Eric Lippert所解释的,编译器选择
DoStuff(IEnumerable)where-Tother:T{}
方法,因为它在检查约束之前选择方法。由于字符串可以执行
IEnumerable
,编译器将其与该子类方法相匹配。 编译器工作正常,如C#规范所述

可以通过将DoStuff实现为。 扩展方法在基类方法之后进行检查,因此它不会尝试将
字符串
DoStuff
IEnumerable
进行匹配,直到它尝试将其与
DoStuff
进行匹配

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

到目前为止,我能想到的最大的缺点是,您不能在覆盖方法中使用
base
,但我认为有办法解决这个问题(询问您是否感兴趣)

使用系统;
使用System.Collections.Generic;
命名空间方法ResolutionExploit
{
公共类BaseContainer:IEnumerable
{
public void DoStuff(T项){Console.WriteLine(“\tbase”);}
公共IEnumerator GetEnumerator(){return null;}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator(){return null;}
}        
公共类容器:BaseContainer{}
公共类ContainerChild:容器{}
公共类ContainerChildWithOverride:Con
        Container<char> c1 = new Container<char>();
        c1.DoStuff("Hello World");

        Container<string> c2 = new Container<string>();
        c2.DoStuff(new List<string>() { "Hello", "World" });
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>()