这是斯巴达,还是呢?

以下是面试问题。 我想出了一个解决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包含具有名称IKtypes参数的嵌套可访问types,则名称空间或types名称指的是用给定的types参数构造的types。 [答对了!]
  • 如果之前的步骤不成功,那么对于每个名称空间N ,从名称空间或名称空间出现,继续每个封闭名称空间(如果有),以全局名称空间结束,以下步骤是评估,直到一个实体的位置:
    • 如果K是零,而IN名字空间的名字,那么… [是的,那成功]

所以最后的要点就是如果第一个项目符号找不到任何东西,那么就是select了Sparta ;但是当基类Place定义了一个接口Sparta我们考虑Sparta之前就会发现它。

请注意,如果您将嵌套types的Place.Sparta为类而不是接口,它仍会编译并返回false – 但编译器会发出警告,因为它知道Sparta的实例永远不会是类的实例Place.Sparta 。 同样,如果将Place.Sparta保留Place.Sparta一个接口,但将Spartasealed ,则会收到警告,因为没有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协议的细节。