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