为什么一个类成员的名称不能与其嵌套类之一相同?

22

或者为什么以下内容是不可能的:

class Material
{
    class Keys
    {
        ...
    }

    Material.Keys Keys { get; set; } // Illegal
}

我没有看到任何可能的歧义。通过实例访问时,返回属性。以静态方式访问时,返回类。或者我有什么遗漏吗?

我不是在要求“修复”(我知道我可以将其命名为MaterialKeys之类的其他名称),而是想了解这个限制背后的技术原因。


这是我想要实现的场景。声明一个内部类并在外部类中声明该类型的成员,这是完全有道理的。外部类仅包含其定义的一个实例。该实例不设计在任何其他上下文中使用,因此将类和成员都放在同一命名空间中(意味着外部类是命名空间...而不是C#命名空间关键字)是有意义的。为了使类和成员都在同一个(外部)类中,我必须给其中一个命名不同。但是我应该给哪个命名奇怪的名字呢? - steve
5个回答

17

但想象一下你有这样的代码:

class Material
{
    class Keys
    {
        ...
    }

    static Material.Keys Keys = new Keys();
}

现在两者都处于"static"作用域。现在,编译器能够在所有情况下消除歧义吗?如果不能,则不允许这样做。

我想可能会对静态字段/属性/方法进行消歧义,而不是针对实例成员。或者反过来。如果是这种情况,您希望语言规范允许实例成员与内部类具有相同的名称,但禁止静态成员吗?这只会令人困惑。

但是,让成员与内部类的名称匹配本身就很令人困惑。


1
非常合理...有道理。谢谢。 - Lazlo
我不认为成员匹配其类型名称或内部类的名称会令人困惑。它们的使用方式不同:一个是类,一个是属性名称。在那里不应该有混淆。这并不否定您的第一个观点,这是非常准确的。没有办法区分 Material.Keys 类和 Material.Keys 静态成员。这基本上是这种机制的破坏者。我想说的是,如果遇到这种情况,应该考虑是否在命名时尽可能描述清楚,例如使用 KeyCollection 而不是 Keys - crush
1
"您是否希望编程语言规范允许实例成员与内部类具有相同的名称,但禁止静态成员使用相同的名称?" 是的,我希望如此。 - drowa

16
"不明确的任何东西都应该合法"绝不是C#语言的设计原则。 C#语言旨在成为一个“高质量坑”;也就是说,语言规则应该将您扔进一个充满明确正确代码的坑中,而您必须努力爬出来,使其变成错误的代码。 “任何不明确的东西都应该合法”的想法在大多数情况下直接违反了“高质量坑”语言的概念。
此外,你认为我需要为不实现功能提供理由的想法是错误的。我们从来不需要实现功能提供理由。相反,必须通过展示它们的好处超过巨大的成本来证明拟议的功能的合理性。功能非常昂贵,我们的预算有限;我们必须只做最好的功能,以产生对我们的客户的好处。
你提出的功能使得易碎和混乱的代码易于生产;它帮助将C#变成“悲伤之坑”语言,而不是“高质量坑”语言。增加脆弱性和混乱性的功能必须增加巨大的好处来补偿这些成本。在您的意见中,这个功能增加的巨大好处是什么,从而使其成本合理? 如果答案是“没有这样的好处”,那么现在你知道为什么语言没有这个功能:因为它会使语言变得更糟。如果有好处,我很乐意考虑它在假设的将来版本的语言中的优点。

4
我并没有暗示有任何巨大的好处。我只是在思考语言学上的语义问题。显然,StackOverflow不仅仅是一个“理解之坑”。 - Lazlo
6
我认为这个功能是有益的。定义一个类和该类型的成员可以很好地实现封装。我理解存在歧义问题,想知道如何解决。也许我们可以接受更多含糊不清的代码(虽然这并不符合 C# 的方式)。或者我们可以为语言添加一些东西来允许这个功能。也许是一种内部类的特殊语法,允许这样做。我不知道会是什么,但需要一些能消除歧义问题的东西。 - steve
2
其中一个方便的用途是当您有一个公开仅在其上下文中相关的枚举的类时。例如,假设我们有一个名为 enum ReportType 的枚举。现在我可能想要一个名为 ReportType 类型为 "enum ReportType" 的属性。我不想在此类之外混杂该枚举,因为我想将其称为 Foo.ReportType.MyFancyReport。创建模型类时这些情况非常普遍。 - arviman
2
@Eric Lippert:好处在于您可以将SQL实体映射到CLR实体而无需使用映射器,例如,“具有字段消息varchar(X)的表消息”,映射到“类消息{public string message;}”当表和列名称由有效的c#标识符组成时。目前,这是不可能的,您需要重命名表或字段,或使用减慢数据提取速度的映射器-所有这些都很糟糕-如果您有一个现有的数据库,其中包含许多现有的查询/更新/接口,则重命名字段/表实际上是不可能的。谈论“质量的深渊”... - Stefan Steiger
2
@Eric Lippert:缺少某些功能也可能是绝望的深渊。 - Stefan Steiger

5

您说过,

在实例访问时返回属性。在静态访问时返回类。

但是如果您只是在 Material 中的某个地方说 Keys,这是静态访问还是实例访问?这是指属性 Keys 还是嵌套类型 Keys?实际上是不明确的。

例如,

class Material
{
    class Keys
    {
        public static int Length;
    }

    string Keys { get; set; }

    public void Process()
    {
        // Does this refer to string.Length (via property Keys)
        // or Material.Keys.Length? It actually refers to both.
        Console.WriteLine(Keys.Length);
    }
}

正如评论中指出的那样,这并不是整个故事,但几乎是。可以有一个名为Color且类型为Color的属性,而且没有任何冲突:

public Color Color { get; set; }

Color.FromName(...)   // refers to static method on the type ‘Color’
Color.ToString()      // refers to instance method on the property’s value

但是这很容易解决,因为当前范围内的事物优先于更外部范围中的事物:
public class MyType { public string FromName(string name) { return null; } }
public MyType Color;

Color.FromName(...)   // unambiguously refers to MyType::FromName(string)
                      // via the property Color

在您的示例中并不容易-嵌套类 Keys 和属性 Keys 在同一范围内(具有相同的声明类型)。你如何决定给哪一个优先级?即使您决定给其中之一优先级,这也只是微不足道的有用,因为您仍然只能拥有两个相同名称的东西,而一个必须是静态的,另一个必须是实例。


2
“this”关键字可以解决这个问题。当我执行“this.Keys.Length”时,会出现歧义异常。 - Lazlo
4
这不是全部的故事;仅凭这一条理由,“public Color Color { get; set; }”也应该是不合法的。 - Ani
@Lazlo - Material.Keys 也引用了你的类型为 Material.Keys 的成员变量,因此出现了问题。 - Michael Shimmins
@Michael Shimmins:我需要看到这个最后语句的歧义示例。我真的不明白你的意思。 - Lazlo
@Ani - 那篇博客完全证实了我所说的。用类型名称来说明差异。仍然相当琐碎。 - Michael Shimmins
显示剩余5条评论

4

我的答案从与其他问题略有不同的角度回答了这个问题。在 C# 语言规范中,以下两个语句:

The same identifier may not be used in different definitions within one scope

并且

The same identifier may not be used in different definitions within one scope, unless it is probably impossible for any ambiguity to arise when the identifier is used

第一段要简单得多。

在语言设计中,简洁是一个关键目标,因为简单的语言更容易让编译器和解释器作者实现,更容易生成和操作工具,更容易让初学者学习,也更容易让程序员理解。在考虑任何语言特性时,该特性增加的复杂性应该被视为负面因素,并且必须通过至少同等量的有用性来平衡。正如您自己所说,允许这样做不会添加任何真正的功能(因为它很容易避开),因此在C#规范中包含它没有令人信服的理由。


1
我同意这个问题很“容易”解决——将成员命名与类型不同。但是,我在这方面遇到了困难。一个会得到有意义的名称,而另一个则会得到一个奇怪的名称——一些派生名称之所以不同只是因为它们需要不同……而不是因为将其命名不同会使代码更可读。例如,如果我将成员命名为Field,那么我该如何命名类型呢?FieldType、FieldClass、_Field?我对“Field”这个词做的任何事情都只会让读者问:为什么类型名称与类名称不同呢? - steve

3
由于嵌套类 KeysMaterial 的成员,而属性 Keys 也是其成员。你有两个名为 Keys 的成员。
同样地,你不能拥有两个同名的属性:
public class Bar
{
    private bool Foo { get; set; }
    private string Foo { get; set; }
}

当你访问Foo时,你是想访问哪一个?
public class Material : Keys
{

    private Keys K { get; set; }
}

public class Keys
{

}

这个功能可以正常工作,但可能不是你想要的。


我同意你不能定义两个同名的实例成员,但为什么不能同时拥有一个同名的静态成员和实例成员呢?你不可能混淆它们。"Material.Keys" 和 "new Material().Keys" 完全不同。 - Lazlo
1
当您从Members内部访问Keys时呢?它是静态的还是成员的?public Material() { Keys. // 哪一个? } - Michael Shimmins
1
我不知道你怎么看,但我的编译器从来没有允许我从实例中分配嵌套类。因此 new Material() { Keys } 明显是实例化的一个。 - Lazlo
在它周围加上反引号。这不是关于赋值,而是关于解析。 - Michael Shimmins
1
@Lazlo,通过实例访问静态成员虽然代码风格不佳,但是是合法的。因此,它们确实会发生冲突。关键的教训是,任何被归类为成员的东西都不能共享相同的名称和封闭类型。 - Kirk Woll

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接