为什么我可以使用类名而不是类实例来调用这个静态方法?

Can*_*n33 1 c# static-methods class method-call member-access

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Eri*_*ert 10

你的困惑是很自然的,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.MainProgram不是一个对象。编译器验证Program引用了一个类型并且类型有成员。再一次,重载决议接管并确定唯一可能的含义Main是正在调用。由于Main是静态的,而接收者——点左边的东西——指的是一种类型,这是允许的。

也许在我正在上的这门课中没有正确解释它。

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

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

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

您没有问过的另一件事很重要:

反射呢?

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

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

Type t = typeof(Program);
Run Code Online (Sandbox Code Playgroud)

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

t.Main();
Run Code Online (Sandbox Code Playgroud)

例如。有办法来调用通过反射的方法,但它是认为的错误Type对象类。它反映了阶级。

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

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

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

我们都想要一款轻便、便宜且能拍出精美照片的相机,但俗话说,你只能拥有两个。C# 的设计者也处于类似的境地:

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

现在是大的:

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

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

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