在特定情况下,我遇到了C#中的重载解析问题。在我的Razor文件中,我有以下内容:
@foreach (var result in Model.Result)
{
@SearchResult(result)
}
@helper SearchResult(IEntity entity)
{
<p>A normal thing</p>
}
@helper SearchResult(IPhoto photo)
{
<p>A photo! Its title is @photo.Title</p>
}
班级结构:
interface IPhoto : IContentItem
interface IContentItem : IEntity
class Photo : ContentItem, IPhoto
class ContentItem : Entity, IContentItem
class Entity, IEntity
传递的实际实例是Photo。
当应该调用SearchResult(IEntity)
时(或者IEntity派生的实例的最具体的重载),每个实例都会调用 SearchResult(IPhoto)
。如何在不必诉诸于此的情况下做我想做的事情?
if (result is IXXX) { SearchResultXXX((IXXX)result) }
else if (result is IYYY) { SearchResultYYY((IYYY)result) }
...
答案 0 :(得分:4)
由于您的界面实施,您遇到了这个问题。与ChrisF points out IPhoto
实现IContentItem
实现IEntity
一样。文章C# in Depth: Overloading提供了重载解析的一个很好的解释,但总结一下:重载忽略了在决定调用哪一个时不能正确的任何方法。来自overload resolution上的Microsoft规范:
重载解析是一种选择最佳解决方案的编译时机制 函数成员调用给定的参数列表和一组 候选职能成员。重载分辨率选择该功能 要在C#中的以下不同上下文中调用的成员:
调用invocation-expression中指定的方法(Section 7.5.5)。调用在object-creation-expression中命名的实例构造函数(第7.5.10.1节)。调用一个 索引器访问器通过元素访问(第7.5.6节)。调用 在表达式中引用的预定义或用户定义的运算符 (第7.2.3节和第7.2.4节)。这些上下文中的每一个都定义了 候选函数成员集和自己的参数列表 独特的方式,如上面列出的部分中详细描述的那样。对于 例如,方法调用的候选集不会 包括标记为覆盖的方法(第7.3节)和基础中的方法 如果派生类中的任何方法是,则class不是候选者 适用(第7.5.5.1节)。
候选函数成员和参数列表一旦出现 确定后,最佳功能成员的选择是相同的 所有情况:
考虑到适用的候选函数成员集,最好 该集中的函数成员位于。如果集合只包含一个 函数成员,那个函数成员是最好的函数 会员。否则,最好的函数成员是一个函数成员 这比所有其他功能成员更好 给定参数列表,前提是将每个函数成员进行比较 所有其他函数成员使用第7.4.2.2节中的规则。如果 没有一个功能成员比其他功能成员更好 函数成员,然后函数成员调用是模糊的 发生编译时错误。以下部分定义了确切的 适用的职能成员和更好的职能的含义 构件。
这里举例说明上述article on overloading中的一些例子。
任何熟悉重载的人都会意识到,在下面的示例中,static void Foo(string y)
将在调用行Foo("text")
时使用。
class Test
{
static void Foo(int x)
{
Console.WriteLine("Foo(int x)");
}
static void Foo(string y)
{
Console.WriteLine("Foo(string y)");
}
static void Main()
{
Foo("text");
}
}
这里有点复杂但更好的更类似于你的问题。编译器将调用Foo(int x)
,因为它查找更好的函数成员规则,这些规则查看(从其他方面)从每个参数到相应参数类型所涉及的转换(第一个方法的int,第二个方法的double。)
class Test
{
static void Foo(int x)
{
Console.WriteLine("Foo(int x)");
}
static void Foo(double y)
{
Console.WriteLine("Foo(double y)");
}
static void Main()
{
Foo(10);
}
}
所有这些都解释了你的情况发生了什么,IEntity
是照片的最佳转换,无论有IPhoto
超载这一事实。这与Razor @helper语法无关。为了说明这一点,下面的扩展方法存在相同的“问题”。
public static class SearchHelper
{
public static MvcHtmlString SearchResult(this HtmlHelper helper,
IEntity entity)
{
return new MvcHtmlString("A normal thing");
}
public static MvcHtmlString SearchResult(this HtmlHelper helper,
IPhoto photo)
{
return new MvcHtmlString("A photo!");
}
}
最后,我在这里介绍的是更简单的情况 - 由于泛型,可选参数,继承层次结构等引起的重载解析还有其他奇怪之处。所以我所看到的所有内容都说明了你有几个选择:
.Where
lambda表达式仅迭代将特定类型传递给相应帮助程序的特定类型。答案 1 :(得分:3)
属性Model.Result
的类型是什么?我的猜测是IEntity
。
决定哪个重载将在编译时而不是运行时完成,因此实例的类型无关紧要,它总是调用SearchResult(IEntity entity)
方法
<强>更新强>
这是解决此问题的一种可能方法:
@foreach (var result in Model.Result)
{
@if(result is IPhoto)
{
@SearchResult(result as IPhoto)
}
else
{
@SearchResult(result)
}
}
答案 2 :(得分:0)
您可以尝试使用双重调度(即:访问者)模式让您更近一点。但是你仍然需要检查它是否是不是IEntity的东西(除非你能控制IEntity界面)。
interface IContentItem {
void Accept(IContentVisitor visitor);
}
class Photo : IPhoto {
void Accept(IContentVisitor visitor) { visitor.Visit(this); }
}
interface IContentVisitor<T>{
T Visit(IPhoto photo);
T Visit(IEntity entity);
}
class ContentVisitor : IContentVisitor<string>{
string Visit(IPhoto photo) { return "<p>A normal thing</p>"; }
string Visit(IEntity entity) { return "<p>A normal thing</p>"; }
}
var visitor = new ContentVisitor();
@foreach (var result in Model.Result)
{
if(result is IContentItem)
result.Accept(visitor);
else //assuming result is IEntity
visitor.Visit(result);
}