这是斯巴达,还是它?

时间:2017-05-22 19:44:22

标签: c# inheritance types namespaces

以下是面试问题。我提出了一个解决方案,但我不确定它为什么会起作用。

问题:

如果不修改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

3 个答案:

答案 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为零且KI中名称空间的名称,那么...... [是的,成功]
    •   
  •   

如果第一个项目符号找不到任何内容,那么最后一个项目点就是选择N ...但是当基类Sparta定义了接口Place,在我们考虑Sparta类之前找到它

请注意,如果您将嵌套类型Sparta设为类而不是接口,它仍会编译并返回Place.Sparta - 但编译器会发出警告,因为它知道{{1永远不会是类false的实例。同样,如果您保留Sparta一个界面但是Place.SpartaPlace.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#中获得名称解析协议的详细信息。