【问题标题】:This is Sparta, or is it?这是斯巴达,还是?
【发布时间】:2017-10-22 14:07:18
【问题描述】:

以下是面试题。我想出了一个解决方案,但我不确定它为什么有效。


问题:

在不修改Sparta 类的情况下,编写一些代码使MakeItReturnFalse 返回false

public class Sparta : Place
{
    public bool MakeItReturnFalse()
    {
        return this is Sparta;
    }
}

我的解决方案:(剧透)

public class Place
{
public interface Sparta { }
}

但是为什么MakeItReturnFalse() 中的Sparta 指的是{namespace}.Place.Sparta 而不是{namespace}.Sparta

【问题讨论】:

  • 能否请您在解决方案中添加剧透警报或其他内容?我很失望,我没有机会自己解决它。问题确实很神奇。
  • 我最初包含了剧透标签,但是在这篇文章的整个生命周期中,它被社区多次编辑掉。对此感到抱歉。
  • 我不喜欢这篇文章的标题;它更适合 codegolf.SE。我们可以将其更改为实际描述问题的内容吗?
  • 可爱的拼图。可怕的面试问题,但可爱的谜题。既然您知道它的工作原理和原因,您应该试试这个更难的版本:stackoverflow.com/q/41319962/88656

标签: c# inheritance types namespaces


【解决方案1】:

但是为什么MakeItReturnFalse() 中的Sparta 指的是{namespace}.Place.Sparta 而不是{namespace}.Sparta

基本上,因为这就是名称查找规则所说的。在 C# 5 规范中,相关的命名规则在第 3.8 节(“命名空间和类型名称”)中。

前几个项目符号 - 截断和注释 - 阅读:

  • 如果命名空间或类型名称的格式为II<A1, ..., AK> [因此在我们的例子中K = 0]
    • 如果 K 为零并且命名空间或类型名称出现在泛型方法声明中[不,没有泛型方法]
    • 否则,如果命名空间或类型名称出现在类型声明中,则对于每个实例类型 T(第 10.3.1 节),从该类型声明的实例类型开始并继续每个实例类型封闭类或结构声明(如果有):
      • 如果K 为零并且T 的声明包含名称为I 的类型参数,则命名空间或类型名称引用该类型参数。 [没有]
      • 否则,如果 namespace-or-type-name 出现在类型声明的主体中,并且 T 或其任何基本类型包含名称为 I 的嵌套可访问类型和K 类型参数,则 namespace-or-type-name 指的是使用给定类型参数构造的类型。 [宾果游戏!]
  • 如果前面的步骤不成功,那么对于每个命名空间N,从出现命名空间或类型名称的命名空间开始,继续每个封闭命名空间(如果有),并以全局命名空间结束,将评估以下步骤,直到找到实体:
    • 如果K 为零且IN 中命名空间的名称,那么... [是的,成功]

如果第一个项目符号没有找到任何东西,那么最后一个项目符号点就是 Sparta ...但是当基类 Place 定义一个接口 Sparta ,它在我们考虑Sparta 类之前被发现。

请注意,如果您将嵌套类型 Place.Sparta 设为类而不是接口,它仍会编译并返回 false - 但编译器会发出警告,因为它知道 Sparta 的实例永远不会是Place.Sparta 类的实例。同样,如果您将Place.Sparta 保留为接口,但将Sparta 类设为sealed,则会收到警告,因为没有Sparta 实例可以实现该接口。

【讨论】:

  • 另一个随机观察:使用原始的Sparta 类,this is Place 返回true。但是,将public interface Place { } 添加到Sparta 类会导致this is Place 返回false。让我头晕目眩。
  • @budi:对,因为前面的子弹再次找到Place作为接口。
【解决方案2】:

将名称解析为其值时,定义的“接近性”用于解决歧义。任何定义是“最接近”的都是选择的。

接口Sparta 在基类中定义。 Sparta 类在包含命名空间中定义。在基类中定义的事物比在同一命名空间中定义的事物“更接近”。

【讨论】:

  • 想象一下,如果名称查找以这种方式工作。然后,如果有人碰巧添加了同名的顶级类,那么包含内部类的工作代码就会被破坏。
  • @dan04:但是,如果有人碰巧添加了 nested 类,那么 包含嵌套类的工作代码就会被破坏与顶级类同名。所以这并不是一个完全“胜利”的场景。
  • @JonSkeet 我想说添加这样一个嵌套类是工作代码有合理理由受到影响的区域的变化,并注意变化。添加一个完全不相关的顶级类要远得多。
  • @JonSkeet 这不只是一个稍微不同的脆基类问题吗?
  • @JoãoMendes:是的,差不多。
【解决方案3】:

美丽的问题!我想为那些每天不使用 C# 的人添加一个稍微长一点的解释……因为这个问题很好地提醒了一般名称解析问题。

取原代码,稍作修改如下:

  • 让我们打印出类型名称,而不是像在原始表达式中那样比较它们(即return this is Sparta)。
  • 让我们在Place 超类中定义接口Athena 来说明接口名称解析。
  • 让我们也打印出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# 中名称解析协议的详细信息。

【讨论】:

    猜你喜欢
    • 2021-09-11
    • 2010-09-08
    • 1970-01-01
    • 1970-01-01
    • 2020-09-23
    • 2021-05-27
    • 1970-01-01
    • 1970-01-01
    • 2021-04-24
    相关资源
    最近更新 更多