以下是面试问题。我提出了一个解决方案,但我不确定它为什么会起作用。
问题:
如果不修改Sparta
类,请编写一些使MakeItReturnFalse
返回false
的代码。
public class Sparta : Place
{
public bool MakeItReturnFalse()
{
return this is Sparta;
}
}
我的解决方案:(SPOILER)
public class Place
{
public interface Sparta { }
}
但为什么Sparta
中的MakeItReturnFalse()
会引用{namespace}.Place.Sparta
而不是{namespace}.Sparta
?
答案 0 :(得分:118)
但为什么
Sparta
中的MakeItReturnFalse()
会引用{namespace}.Place.Sparta
而不是{namespace}.Sparta
?
基本上,因为这就是名称查找规则所说的内容。在C#5规范中,相关的命名规则在3.8节(“命名空间和类型名称”)中。
前几个子弹 - 截断和注释 - 读取:
- 如果名称空间或类型名称的格式为
I
或格式为I<A1, ..., AK>
[因此在我们的情况下K = 0] :
- 如果K为零且名称空间或类型名称出现在通用方法声明 [nope,no generic methods]
中- 否则,如果namespace-or-type-name出现在类型声明中,那么对于每个实例类型T(第10.3.1节),从该类型声明的实例类型开始并继续每个实例类型封闭类或结构声明(如果有):
- 如果
K
为零且T
的声明包含名称为I
的类型参数,则namespace-or-type-name引用该类型参数。的 [都能跟得上] 强>- 否则,如果namespace-or-type-name出现在类型声明的正文中,并且
T
或其任何基本类型包含名为{的嵌套可访问类型{1}}和I
类型参数,然后 namespace-or-type-name 指的是使用给定类型参数构造的类型。的 [宾果!] 强>- 如果前面的步骤不成功,那么,对于每个名称空间
K
,从发生名称空间或类型名称的名称空间开始,继续使用每个封闭的名称空间(如果有的话),并以全局命名空间,将评估以下步骤,直到找到实体:
- 如果
N
为零且K
是I
中名称空间的名称,那么...... [是的,将成功] 强>
如果第一个项目符号找不到任何内容,那么最后一个项目点就是选择N
类 ...但是当基类Sparta
定义了接口Place
,在我们考虑Sparta
类之前找到它。
请注意,如果您将嵌套类型Sparta
设为类而不是接口,它仍会编译并返回Place.Sparta
- 但编译器会发出警告,因为它知道{{1永远不会是类false
的实例。同样,如果您保留Sparta
一个界面但是Place.Sparta
类Place.Sparta
,则会收到警告,因为没有Sparta
实例可以实现该界面。
答案 1 :(得分:22)
将名称解析为其值时,定义的“接近度”用于解决歧义。无论“最接近”的定义是选择的定义。
接口 G.mediaPlayer.setLooping(true);
在基类中定义。类 G.mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
}
});
在包含的命名空间中定义。在基类中定义的内容比在同一名称空间中定义的内容“更接近”。
答案 2 :(得分:1)
美丽的问题!我想为那些每天不做C#的人添加一个稍微长一点的解释......因为这个问题很好地提醒了一般的名称解析问题。
采用原始代码,通过以下方式稍作修改:
return this is Sparta
)。Athena
超类中定义接口Place
来说明接口名称解析。 this
类中绑定的Sparta
类型名称,只是为了让一切都清楚。代码如下所示:
public class Place {
public interface Athena { }
}
public class Sparta : Place
{
public void printTypeOfThis()
{
Console.WriteLine (this.GetType().Name);
}
public void printTypeOfSparta()
{
Console.WriteLine (typeof(Sparta));
}
public void printTypeOfAthena()
{
Console.WriteLine (typeof(Athena));
}
}
我们现在创建一个Sparta
对象并调用这三种方法。
public static void Main(string[] args)
{
Sparta s = new Sparta();
s.printTypeOfThis();
s.printTypeOfSparta();
s.printTypeOfAthena();
}
}
我们获得的输出是:
Sparta
Athena
Place+Athena
但是,如果我们修改Place类并定义接口Sparta:
public class Place {
public interface Athena { }
public interface Sparta { }
}
然后是Sparta
- 接口 - 首先可用于名称查找机制,我们的代码输出将更改为:
Sparta
Place+Sparta
Place+Athena
所以我们只是通过在超类中定义Sparta接口来有效地搞清楚MakeItReturnFalse
函数定义中的类型比较,该接口首先通过名称解析找到。
但是为什么C#选择在名称解析中优先考虑超类中定义的接口? @JonSkeet知道!如果您阅读了他的答案,您将在C#中获得名称解析协议的详细信息。