【问题标题】:Why can't a class member's name be the same as one of its nested classes?为什么类成员的名称不能与其嵌套类之一相同?
【发布时间】:2011-06-11 12:06:57
【问题描述】:

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

class Material
{
    class Keys
    {
        ...
    }

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

我没有看到任何可能的歧义。通过实例访问时,返回属性。静态访问时,返回类。还是我错过了什么?

我不是要求“修复”(我知道我可以换个名字,比如 MaterialKeys 等),但更多的是这个限制背后的技术原因。

【问题讨论】:

  • 这是我希望看到的工作场景。我想声明一个内部类并在外部类中声明该类型的成员是完全有道理的。外部类恰好包含它定义的事物的一个实例。那件事不是为了在任何其他上下文中工作而设计的,所以类和成员都在同一个命名空间中是有意义的(这意味着外部类是命名空间......而不是 c# 命名空间关键字)。要将类和成员都放在同一个(外部)类中,我必须命名其中一个。但是我想要一个奇怪的名字呢?

标签: c# properties conflict nested-class


【解决方案1】:

因为嵌套类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”完全不同。
  • 当您从Members 内部访问Keys 时会怎样?它是哪一个,静态的还是成员的? public Material() { Keys. // which one? }
  • 我不知道,但我的编译器从未允许我从实例中分配嵌套类。因此new Material() { Keys } 肯定是实例化的。
  • 在它周围加上反引号。它不是关于分配,而是关于分辨率。
  • @Lazlo,通过实例访问静态成员是合法(尽管代码风格不佳)。因此,它们确实会发生碰撞。 key 的教训是,任何被归类为 member 的东西都不能共享相同的名称和封闭类型。
【解决方案2】:

你说,

通过实例访问时,返回属性。静态访问时,返回类。

但是如果你在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);
    }
}

正如 cmets 中所指出的,这并不是故事的全部。但几乎。拥有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 在同一范围内(具有相同的声明类型)。你如何决定优先考虑哪个?即使您确实决定优先考虑其中一个,这也只会有一点用处,因为您仍然只能拥有两个同名的东西,一个必须是静态的,另一个必须是实例。

【讨论】:

  • “this”关键字可以解决这个问题。当我执行“this.Keys.Length”时,我得到一个模棱两可的异常。
  • 这还不是全部;纯粹基于这个推理,public Color Color { get; set; } 也应该是非法的。
  • @Ani - 当您在该上下文中使用 Color 时,它将首先使用成员实例。使用 System.Drawing.Color 的完全限定名称将使您能够访问 Color 类。这不是同一个问题,因为您的班级仍然只有一个名为Color 的成员,它恰好使用了另一个名为Color 的班级。区分它们是微不足道的。
  • @Lazlo 当然,this.Keys 适用于该属性,但无法明确引用 class Keys。这是一个问题——你应该总是能够明确地引用一个类。
  • @Michael Shimmins:“区分它们是微不足道的。”并不真地。 blogs.msdn.com/b/ericlippert/archive/2009/07/06/…
【解决方案3】:

但是想象一下你有这个:

class Material
{
    class Keys
    {
        ...
    }

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

现在两者都处于“静态”范围。现在,编译器可以在所有情况下消除歧义吗?如果不是,那么这是不允许的。

我认为消歧可能适用于静态字段/属性/方法,而不适用于实例成员。或者反过来。如果是这种情况,您是否希望语言规范允许实例成员与内部类具有相同的名称,但不允许它用于静态?那只会令人困惑。

但是,无论如何,让一个成员与内部类的名称匹配是相当令人困惑的。

【讨论】:

  • 非常合理...原因。谢谢。
  • 我不会说成员匹配其类型名称或内部类的名称会令人困惑。它们的使用方式不同:一个是类,一个是属性名。那里不应该有混乱。这并不否定你的第一点,这是正确的。没有办法区分 Material.Keys 类和 Material.Keys 静态成员。对于这种机制来说,这几乎是一个交易破坏者。我想说的是,如果有人遇到这种情况,他们应该考虑他们的命名是否尽可能具有描述性。 KeyCollection 而不是 Keys
  • “您是否希望语言规范允许实例成员与内部类具有相同的名称,但不允许它用于静态?”是的,我愿意。
【解决方案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# 规范。

【讨论】:

  • 我同意解决问题容易 -- 将成员命名为不同于类型。但是,我正在为此苦苦挣扎。一个会得到有意义的名字,另一个会得到一个奇怪的名字——一些不同的派生词只是因为它需要不同……而不是因为不同的命名会使代码更具可读性。例如,如果我命名成员字段,我该如何命名类型?字段类型、字段类、_字段?除了让读者问:为什么类型名与类名不同?
【解决方案5】:

“任何不模棱两可的东西都应该是合法的”绝对不是 C# 语言的设计原则。 C#语言被设计成一个“质量坑”的语言;也就是说,语言的规则应该把你扔进一个充满明显正确代码的坑里,你必须努力爬出这个坑才能把它变成不正确的代码。在大多数情况下,“任何不明确的东西都应该是合法的”的想法直接违背了“质量坑”语言的概念。

此外,您认为我需要为您提供不做某项功能的理由的想法是倒退的。我们永远不需要为做某项功能提供理由。相反,所提议的功能必须通过证明其收益超过其巨大成本来证明其合理性。功能非常昂贵,我们的预算有限;我们必须只做最好的功能才能为我们的客户带来好处。

您提出的功能可以轻松生成易碎且令人困惑的代码;它有助于使 C# 成为“绝望之坑”语言,而不是“质量之坑”语言。给语言增加脆弱性和混乱的特性必须增加巨大的好处来补偿这些成本。 在您看来,这个特性给语言带来的巨大好处是什么?

如果答案是“没有这样的好处”,那么现在您知道为什么该语言没有该功能了:因为它使语言变得更糟,净。

如果有好处,我很乐意考虑它对于假设的未来语言版本的优点。

【讨论】:

  • 我并没有暗示有什么很大的好处。我只是想知道语言的语义。显然 StackOverflow 不仅仅是一个“理解的坑”。
  • 我确实认为该功能有好处。可以定义一个类和该类型的成员的类有助于良好的封装。我确实理解模棱两可的问题,我想知道如何解决这个问题。也许我们生活在更模棱两可的代码中(尽管那不是 C# 方式)。或者我们在语言中添加一些东西来允许这个特性。也许是允许这样做的内部类的特殊语法。我不知道它会是什么,但可以消除模棱两可的问题。
  • 一个方便的用途是当你有一个类公开一个只属于它的上下文的枚举时。例如,假设我们有一个enum ReportType。现在我可能想要一个类型为“枚举 ReportType”的名为 ReportType 的属性。我不想在这个类之外把那个枚举弄乱,因为我想把它称为Foo.ReportType.MyFancyReport。在创建模型类时,这种情况很常见。
  • @Eric Lippert:好处是您实际上可以将 SQL 实体映射到 CLR 实体,而无需映射器。例如"table message with field message varchar(X)",当表和列名由有效的 c# 标识符组成时,映射到 "class message{ public string message;}"。现在,这是不可能的,您需要重命名表或字段,或者使用减慢数据获取速度的映射器 - 所有这些都很糟糕 - 如果您有一个包含许多现有查询/更新的现有数据库/interfaces,重命名字段/表是不可能的。谈论“质量坑”...
  • @Eric Lippert:没有某些功能也可能是绝望的深渊。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-21
  • 1970-01-01
  • 2020-08-24
  • 2012-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多