这是斯巴达,还是?


121

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


题:

在不修改Sparta类的情况下,编写使MakeItReturnFalsereturn的代码false

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

我的解决方案:(SPOILER)

public class Place
{
public interface Sparta { }
}

但是,为什么SpartaMakeItReturnFalse(){namespace}.Place.Sparta代替{namespace}.Sparta


5
您能否将Spoiler Alert或其他内容添加到解决方案中?我很失望,我没有机会自己解决。问题确实令人惊讶。
卡洛里斯·卡耶纳斯

1
我最初加入了剧透标签,但是在整个帖子的生命周期中,社区多次对其进行了编辑。对于那个很抱歉。
布迪

1
我不喜欢这篇文章的标题;它更适合codegolf.SE。我们可以将其更改为实际描述问题的内容吗?
Supuhstar

3
可爱的难题。可怕的面试问题,但可爱的难题。现在您知道了它的工作原理和原因,现在应该尝试使用此更难的版本:stackoverflow.com/q/41319962/88656
Eric Lippert

Answers:


117

但是,为什么SpartaMakeItReturnFalse(){namespace}.Place.Sparta代替{namespace}.Sparta

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

前几对子弹-被截断和注释-阅读:

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

因此,如果第一个项目符号什么也没找到,那么最后一个要点就是选择Sparta 该类 ……但是当基类Place定义了一个接口时Sparta,它将我们考虑Sparta该类之前被找到。

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


2
另一个随机观察结果:使用原始Sparta类,this is Place返回true。但是,添加public interface Place { }Sparta类会导致this is Placereturn false。使我的头旋转。
布迪

@budi:是的,因为再次,较早的项目符号被找到Place作为界面。
乔恩·斯基特

22

将名称解析为其值时,定义的“紧密度”用于解决歧义。无论“最接近”的定义是选择的那个。

该接口Sparta在基类中定义。该类Sparta在包含名称空间中定义。在基类中定义的事物比在相同命名空间中定义的事物“更紧密”。


1
并想象一下,如果名称查找不能以这种方式工作。然后,如果有人偶然添加具有相同名称的顶级类,则包含内部类的工作代码将被破坏。
dan04 '17

1
@ dan04:但是,如果有人碰巧添加了一个与顶级类同名的嵌套类,那么不包含嵌套类的工作代码就会损坏。因此,这并非完全是“赢”的情况。
乔恩·斯基特

1
@JonSkeet我要说的是,添加这样的嵌套类是对工作代码有合理理由会受到影响并注意更改的区域的更改。添加一个完全不相关的顶级类将被删除。
Angew不再为

2
@JonSkeet难道这不只是脆性基类问题的掩饰吗?
若奥·门德斯

1
@JoãoMendes:是的,差不多。
乔恩·斯基特

1

美丽的问题!对于那些每天都不使用C#的人,我想添加一个稍长的解释...因为这个问题通常可以很好地提醒您解决名称解析问题。

采用原始代码,并通过以下方式对其进行了稍微的修改:

  • 让我们打印出类型名称,而不是像原始表达式(即return this is Sparta)中那样比较它们。
  • 让我们AthenaPlace超类中定义接口以说明接口名称解析。
  • 让我们也打印出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

因此,MakeItReturnFalse仅在超类中定义Sparta接口即可有效地弄乱了函数定义中的类型比较,该 接口首先是通过名称解析找到的。

但是,为什么C#选择优先考虑名称解析中超类中定义的接口?@JonSkeet知道!而且,如果您阅读了他的回答,您将获得C#中名称解析协议的详细信息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.