为什么我可以使用 class 名称而不是 class 实例来调用此静态方法?

Why can I call this static method using the class name but not using a class instance?

class Program
{
    static void Main(string[] args)
    {
        var p = new Program();
        p.Main(args);//instance reference error,use type name instead

        var p = new Program();
        Program.Main(args);//error disappears
    }
}

我想我知道静态与对象实例无关,但我遇到的问题是 classes 不是对象的同义词吗?还是 classes 不用于创建对象?那么,如果 classes 本质上是对象,为什么当我使用 class 名称时错误消失了?

我知道我还没有创建 Main 的实例,以后也不会。这是唯一不同的东西吗?也许我正在接受的 class 中没有正确解释。

您的困惑是很自然的,而 C# 在这方面的设计加剧了这种困惑。我会尽量解释,我会重新表述你的问题以便更容易回答:

Is class synonymous with object?

没有。让我们在这一点上非常非常清楚。 "Object"在C#中有特定的含义。对象始终是类型 的实例。 C# 中有两大类对象:引用类型,它们是通过引用复制,如string,值类型是按值复制,如int

稍后您将了解 装箱,这是一种机制,值类型的实例可以在需要引用的上下文中使用,但不用担心暂时这样。

在 C# 中,class 定义了一个 引用类型 实例 class 是对象。 class 本身不是对象。

这样做的理由来自现实世界。 class "all objects which are newspapers" 本身不是报纸。 class "all people who speak French" 本身不是讲法语的。将对一组事物的描述事物的具体例子混淆是类别错误!

(你不妨仔细研究一下原型继承语言的设计,比如JavaScript。在JS中我们做了一个具体的对象,就是一类事物的原型示例,我们为这种事物的新示例制作一个代表工厂的构造函数对象;原型和构造函数一起工作创建新实例,两者都是真正的对象。但同样,你的问题是关于 C# 的,所以让我们暂时坚持下去。)

are classes used in creating objects?

是的。我们new实例化一个class;由于所有 classes 都是引用类型,new 生成对新对象的引用。

So why does the error disappear when i use the class name, if classes are essentially objects?

类 不是对象,但我理解你的困惑。看起来 class 名称确实在您期望对象的上下文中使用。 (您可能有兴趣仔细检查 Python 等语言的设计,其中 classes 对象,但您的问题是关于 C# 的,所以让我们坚持下去。 )

要解决此混淆,您需要了解 成员访问运算符 ,也称为 "dot operator",在 C# 中是最灵活和最复杂的运算符之一。这使得它易于使用但难以理解!

要理解的关键是成员访问运算符始终具有这种形式:

  • 在点的左边是一个表达式,计算出一个有成员的事物
  • 圆点右边是简称.
  • 虽然 thing 是一个对象并且 thing.name 是一个对象是可能的,而且很常见,但其中一个或两个 不是 是一个对象。

当你说 p.Main 时,编译器会说 "I know that p is an instance of Program, and I know that Main is a name. Does that make sense?"

编译器做的第一件事是验证 Program —— p 的类型 —— 有一个可访问的成员 Main,它确实这样做了。此时 overload resolution 接管,我们发现 Main 唯一可能的含义是静态方法。 这可能是个错误,因为p 是一个实例,而我们正试图通过该实例调用静态。 C# 的设计者本可以允许这样做——在其他语言中也是允许的。但由于这可能是一个错误,他们不允许这样做。

当您键入 Program.Main 时,Program 不是对象 。编译器验证 Program 引用类型并且 类型具有成员 。重载决议再次接管,它确定唯一可能的含义是 Main 正在被调用。因为 Main 是静态的并且接收者——点左边的东西——指的是一个类型,所以这是允许的。

Maybe it's just not being explained properly in this class I'm taking.

我编辑技术书籍和其他课程材料,其中很多对这些概念的解释都很糟糕。此外,许多教师对 class 元素、对象、变量等之间的关系有模糊和混淆的概念。我鼓励您就这些问题仔细询问您的导师,直到您对他们的解释感到满意为止。

也就是说,一旦你对这些事情有了扎实的把握,你就可以开始走捷径了。作为专家 C# 程序员,我们说“p 是一个对象...”因为我们 all 知道我们的意思是“p 是一个变量,值这是对对象的引用..."

我觉得把它拼出来对初学者有帮助,但你很快就会变得更轻松。

另一件你没有问但很重要的事情:

What about reflection?

.NET 有一个 反射 系统,它允许你获取 不是 对象的东西,比如 classes,结构、接口、方法、属性、事件等,并获得描述它们的对象。 (类比是镜像不是现实但它看起来确实足以理解现实。)

重要的是要记住反射对象是 而不是 class。它是 描述 class 的对象。如果您像这样在程序中使用反射:

Type t = typeof(Program);

那么t的值是对描述classProgram特性的Type对象的引用。您可以检查该对象并确定方法 Main 有一个 MethodInfo,依此类推。但是对象 不是 class。你不能说

t.Main();

例如。有一些方法可以通过反射调用方法,但是将 Type 对象视为 成为 class 是错误的。它 反映了 class。

另一个你没有问但与你的教育密切相关的问题:

What you're saying here is that values are instances of objects, but certain programming language constructs such as classes are not objects that can be manipulated like values. Why is it that some programming language constructs in C# are "first class" -- they can be treated as data manipulated by the program -- and some are "second class", and cannot be so manipulated?

这个问题触及了语言设计本身的关键。所有的语言设计都是一个审视过去语言的过程,观察它们的长处和短处,提出试图在优势和弱点的基础上建立的原则,然后解决当原则相互冲突时所产生的无数矛盾。

我们都想要一台轻巧、便宜、能拍出好照片的相机,但俗话说,你只能拥有两台。 C# 的设计者处于类似的位置:

  • 我们希望语言有少量新手必须理解的概念。此外,我们通过不同的概念统一到层次结构中来实现这一点;结构、classes、接口、委托和枚举都是 类型。 if、while、foreach都是语句。等等。
  • 我们希望能够构建程序,以强大的方式操纵对开发人员来说很重要的值。例如,制作函数 "first class" 开辟了强大的新编程方式。
  • 我们希望程序有足够少的冗余,让开发人员不会觉得他们被迫进行不必要的仪式,但要有足够的冗余,让人们能够理解所写的程序。

现在大的:

  • 我们希望语言足够通用,以允许业务线开发人员编写代表其业务领域中任何概念的程序
  • 我们希望机器可以理解语言,这样机器甚至可以在程序运行之前发现可能的问题。也就是说,语言必须静态可分析

但就像相机一样,您无法同时拥有所有这些。 C# 是一种通用编程语言,专为大型编程而设计,具有强大的静态类型检查器,可在实际错误投入生产之前发现它们。恰恰是 因为 我们想要找到您遇到的那种错误,所以我们 不允许 将类型视为第一个 class 值。如果您首先将类型视为 class,那么大量的静态分析功能就会消失 window!

其他语言设计者做出了完全不同的选择; Python 说 "classes are a kind of function, and functions are a kind of object" 的后果极大地使语言朝着我们期望的分层简单性和首先 class 处理语言概念的目标移动,并且大大远离静态确定正确性的能力。这是 Python 的正确选择,但不是 C#。

classes 中的静态方法旨在连接到 class。其他方法旨在连接到对象。这样,您无需创建对象即可访问静态方法。在 C++ 中,这是您将使用的样板代码:

className::staticMethod();