1
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
    }
}

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

我知道我还没有创建一个实例,Main也不会。这是唯一的区别吗?也许我正在上的这门课没有正确解释。

4

2 回答 2

10

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

class同义词object吗?

不,让我们在这一点上非常非常清楚。“对象”在 C# 中具有特定含义。对象始终是类型的实例。C# 中有两种广泛的对象:通过引用复制的引用类型,如和通过值复制的值类型,如。stringint

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

在 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 方法,依此类推。但对象不是类。你不能说TypeProgramMethodInfoMain

t.Main();

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

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

您在这里所说的是是对象的实例,但某些编程语言结构(例如类)不是可以像值一样被操作的对象。为什么 C# 中的某些编程语言结构是“第一类”——它们可以被视为程序操作的数据——而有些是“第二类”,不能被如此操作?

这个问题触及了语言设计本身的症结所在。所有的语言设计都是一个检查过去语言的过程,观察它们的优势和劣势,提出原则,试图建立优势,减少劣势,然后解决原则相互冲突时所带来的无数矛盾。

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

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

现在是大的:

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

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

其他语言设计者做出了完全不同的选择;Python 所说的“类是一种函数,函数是一种对象”的结果,极大地推动了语言朝着我们期望的目标——层次简单性和对语言概念的一流处理——极大地偏离了静态确定正确性的能力. 这对于 Python 来说是正确的选择,但对于 C# 来说却不是。

于 2020-03-18T19:43:01.110 回答
-1

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

className::staticMethod();
于 2021-09-30T03:46:54.260 回答