这是斯巴达,还是呢?
以下是面试问题。 我想出了一个解决scheme,但我不知道为什么它的作品。
题:
在不修改Sparta
类的情况下,编写一些使MakeItReturnFalse
返回false
代码。
public class Sparta : Place { public bool MakeItReturnFalse() { return this is Sparta; } }
我的解决scheme
public class Place {public interface Sparta {}}
但为什么MakeItReturnFalse()
中的Sparta
引用{namespace}.Place.Sparta
而不是{namespace}.Sparta
?
但为什么
MakeItReturnFalse()
中的Sparta
引用{namespace}.Place.Sparta
而不是{namespace}.Sparta
?
基本上,因为这就是名称查找规则所说的。 在C#5规范中,相关的命名规则在3.8节(“命名空间和types名称”)中。
头几个子弹 – 截断和注释 – 读取:
- 如果名称空间或types名称的forms为
I
或forms为I<A1, ..., AK>
[在我们的例子中K = 0] :
- 如果K为零,并且名称空间或types名称出现在通用方法声明中[nope,no generic methods]
- 否则,如果名称空间或types名称出现在types声明中,则对于每个实例typesT(第10.3.1节),从该types声明的实例types开始,继续每个封闭类的实例types,或者结构声明(如果有):
- 如果
K
为零,并且T
的声明包含名称为I
的types参数,则名称空间或types名称引用该types参数。 [不]- 否则,如果名称空间或types名称出现在types声明的主体中,并且
T
或其任何基types包含具有名称I
和K
types参数的嵌套可访问types,则名称空间或types名称指的是用给定的types参数构造的types。 [答对了!]- 如果之前的步骤不成功,那么对于每个名称空间
N
,从名称空间或名称空间出现,继续每个封闭名称空间(如果有),以全局名称空间结束,以下步骤是评估,直到一个实体的位置:
- 如果
K
是零,而I
是N
名字空间的名字,那么… [是的,那会成功]
所以最后的要点就是如果第一个项目符号找不到任何东西,那么就是select了Sparta
类 ;但是当基类Place
定义了一个接口Sparta
, 在我们考虑Sparta
类之前就会发现它。
请注意,如果您将嵌套types的Place.Sparta
为类而不是接口,它仍会编译并返回false
– 但编译器会发出警告,因为它知道Sparta
的实例永远不会是类的实例Place.Sparta
。 同样,如果将Place.Sparta
保留Place.Sparta
一个接口,但将Sparta
类sealed
,则会收到警告,因为没有Sparta
实例可以实现该接口。
将名称parsing为其值时,将使用定义的“接近度”来解决歧义。 无论什么定义是“最接近”的是被选中的。
接口Sparta
是在一个基类中定义的。 在包含的命名空间中定义了类Sparta
。 在基类中定义的东西比在同一个名字空间中定义的东西“更接近”。
美丽的问题! 对于那些每天不做C#的人,我想补充一点稍微长一点的解释…因为这个问题总体上是对名称parsing问题的一个很好的提醒。
拿原来的代码,用下面的方法稍加修改:
- 让我们打印出types名称,而不是像在原始expression式中那样比较它们(即
return this is Sparta
)。 - 我们来定义
Place
超类中的接口Athena
来说明接口名称parsing。 - 让我们也打印出这个types名称,因为它是在
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)); } }
我们现在创build一个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
所以,我们已经在MakeItReturnFalse
函数定义中通过定义超类中的Sparta接口来有效地与types比较MakeItReturnFalse
了。
但为什么C#select在名称parsing中定义超类中定义的接口? @JonSkeet知道! 如果你阅读他的答案,你会得到在C#中的名称parsing协议的细节。