您的困惑是很自然的,C# 在这方面的设计加剧了这种困惑。我会尽量解释,我会重新表述你的问题以便更容易回答:
是class
同义词object
吗?
不,让我们在这一点上非常非常清楚。“对象”在 C# 中具有特定含义。对象始终是类型的实例。C# 中有两种广泛的对象:通过引用复制的引用类型,如和通过值复制的值类型,如。string
int
稍后您将了解装箱,这是一种机制,值类型的实例可以在需要引用的上下文中使用,但现在不要担心。
在 C# 中,一个类定义了一个引用类型。该类的实例是对象。类本身不是对象。
这样做的理由来自现实世界。“所有属于报纸的对象”这一类本身并不是报纸。“所有会说法语的人”这个班级本身并不是说法语的人。将一组事物的描述与该事物的特定示例混淆是一个类别错误!
(您可能希望仔细检查原型继承语言的设计,例如 JavaScript。在 JS 中,我们创建一个特定对象,它是一种事物的原型示例,我们创建一个构造函数对象,代表该工厂的新示例。之类的东西;原型和构造函数一起工作以创建新实例,并且两者都是真正的对象。但同样,您的问题是关于 C#,所以现在让我们坚持下去。)
是用于创建对象的类吗?
是的。我们用;实例化一个类 new
由于所有类都是引用类型,因此new
会产生对新对象的引用。
那么,如果类本质上是对象,为什么当我使用类名时错误会消失?
类不是对象,但我理解你的困惑。看起来类名肯定是在您期望一个对象的上下文中使用的。(您可能有兴趣仔细研究类是对象的 Python 等语言的设计,但您的问题是关于 C#,所以让我们坚持下去。)
要解决这种混淆,您需要了解 C# 中的成员访问运算符(也称为“点运算符”)是最灵活和最复杂的运算符之一。这使它易于使用但难以理解!
要理解的关键是成员访问运算符始终具有这种形式:
- 点的左边是一个表达式,它计算出一个有成员的事物
- 点的右边是一个简单的名字。
- 尽管既是
thing
客体又thing.name
是客体是可能且常见的,但也有可能其中一个或两者都不是客体。
当你说p.Main
编译器说“我知道那p
是 的一个实例Program
,我知道那Main
是一个名字。这有意义吗?”
编译器做的第一件事是验证Program
--p
的类型 -- 有一个可访问的成员Main
,它确实做到了。此时重载决议接管,我们发现唯一可能的含义Main
是静态方法。这很可能是一个错误,因为p
它是一个实例,而我们正试图通过实例调用一个静态变量。C# 的设计者本可以允许这样做——其他语言也允许这样做。但由于这可能是一个错误,他们不允许这样做。
当你键入Program.Main
时,Program
不是一个对象。编译器验证Program
引用了一个类型并且类型有成员。再一次,重载决议接管并确定唯一可能的含义是Main
正在被调用。因为Main
是静态的,并且接收者——点左边的东西——指的是一个类型,所以这是允许的。
也许我正在上的这门课没有正确解释。
我编辑技术书籍和其他课程材料,其中很多对这些概念的解释很差。此外,许多教师对类、对象、变量等之间的关系有着模糊和混乱的概念。我鼓励你就这些问题仔细询问你的导师,直到你对他们的解释感到满意为止。
也就是说,一旦你对这些事情有了扎实的把握,你就可以开始走捷径了。作为专业的 C# 程序员,我们说“p
是一个对象......”因为我们都知道我们的意思是“p
是一个变量,其值是对一个对象的引用......”
我认为把它拼出来对初学者很有帮助,但你很快就会变得更加放松。
您没有问但很重要的另一件事:
反射呢?
.NET 有一个反射系统,它允许您获取非对象的东西,如类、结构、接口、方法、属性、事件等,并获取描述它们的对象。(比喻是镜像不是现实,但它看起来确实足以理解现实。)
重要的是要记住反射对象不是类。它是一个描述类的对象。如果您像这样在程序中使用反射:
Type t = typeof(Program);
那么 的值是对描述类特征的对象t
的引用。您可以检查该对象并确定存在for 方法,依此类推。但对象不是类。你不能说Type
Program
MethodInfo
Main
t.Main();
例如。有一些方法可以通过反射来调用方法,但是将Type
对象视为类是错误的。它反映了阶级。
另一个您没有问但与您的教育密切相关的问题:
您在这里所说的是值是对象的实例,但某些编程语言结构(例如类)不是可以像值一样被操作的对象。为什么 C# 中的某些编程语言结构是“第一类”——它们可以被视为程序操作的数据——而有些是“第二类”,不能被如此操作?
这个问题触及了语言设计本身的症结所在。所有的语言设计都是一个检查过去语言的过程,观察它们的优势和劣势,提出原则,试图建立优势,减少劣势,然后解决原则相互冲突时所带来的无数矛盾。
我们都想要一台轻便、便宜、能拍出好照片的相机,但俗话说得好,你只能拥有两个。C# 的设计者也处于类似的位置:
- 我们希望语言具有少量必须由新手理解的概念。此外,我们通过将不同的概念统一到层次结构中来实现这一点;结构、类、接口、委托和枚举都是类型。if、while、foreach 都是语句。等等。
- 我们希望能够构建程序,以强大的方式操纵对开发人员很重要的值。例如,将函数设为“一流”开辟了强大的新编程方式。
- 我们希望程序有足够少的冗余,开发人员不会觉得他们被迫进行不必要的仪式,但有足够的冗余,人类可以理解程序的编写。
现在是大的:
- 我们希望语言足够通用,以允许业务线开发人员编写代表其业务领域中任何概念的程序
- 我们希望机器能够理解这种语言,以至于机器甚至可以在程序运行之前发现可能的问题。也就是说,语言必须是可静态分析的。
但就像相机一样,你不可能同时拥有所有这些。C# 是一种通用编程语言,专为大型编程而设计,具有强大的静态类型检查器,可在实际错误投入生产之前发现它们。正是因为我们想要找到您遇到的那种错误,我们才不允许将类型视为一等值。如果您将类型视为第一类,那么大量的静态分析功能就会消失!
其他语言设计者做出了完全不同的选择;Python 所说的“类是一种函数,函数是一种对象”的结果,极大地推动了语言朝着我们期望的目标——层次简单性和对语言概念的一流处理——极大地偏离了静态确定正确性的能力. 这对于 Python 来说是正确的选择,但对于 C# 来说却不是。