不需要将对象名称传递给构造函数吗? "new Class()" 还不是对象

Don't you need an object name to pass into the constructor? "new Class()" isn't an object yet

我一直在用一些 Udemy 课程来补充我的学校课程。我现在正试图理解一个用于中级 C# 的示例,但我对其中一个细节有点困惑。我理解继承,但我对组合有点困惑(特别是将对象传递给对象以及它们在何处初始化)。在此示例中,有一个 DbMigrator class、Installer Class 和一个 DbMigrator 和 Installer 都借用的 Logger class。这是主要内容:

namespace Composition 
{
    class Program 
    {
        static void Main(string[] args)
        {
            var dbMigrator = new DbMigrator(new Logger());
            var logger = new Logger();
            var installer = new Installer(logger);

            dbMigrator.Migrate();
            installer.Install();
        }
    }
}

我理解您创建传递给安装程序对象的记录器对象的部分,但我没有得到 dbMigrator 对象的 "new Logger()" 构造函数,因为记录器没有变量名正在创建。您不需要在 dbMigrator 对象的构造函数中创建 "var logger1 = new Logger()" 吗?我不明白你怎么能在没有变量名的情况下传递 "new logger()"。

这是 DbMigrator class:

namespace Composition
{
    public class DbMigrator
    {
        private readonly Logger _logger;
        public DbMigrator(Logger logger)
        {
            _logger = logger;
        }

        public void Migrate()
        {
            _logger.log = ("Blah blah blah");
        }
    }
}

我知道 DbMigrator class 已经初始化了一个,但我认为需要先创建一个对象。

有人可以帮我解决这个问题吗?

首先,总结一下这个问题 - 这是怎么回事?

var dbMigrator = new DbMigrator(new Logger());

不应该是这个吗?

var logger = new Logger();
var dbMigrator = new DbMigrator(logger);

一个完整详细的答案很长,所以我会尽力让它变得漂亮且易于理解。为此,我们将首先描述什么是 expression 以及它们如何在 C# 中工作(更广泛地说,它们如何在任何基于堆栈的语言中工作 - 幸运的是,这几乎是您所使用的所有语言)我听说过!)

表达式、语句和操作..什么?

一个运算:像加减法等

一个表达式:一堆操作1+2+3 或只是一个常量,如 1"hi!"

一个语句: 通常一个表达式后跟一个 ; 但类似 ifwhile 循环的东西也是语句。如果一个程序是一个烹饪食谱,那么语句只是其中的一个步骤。

让我们在 C# 中快速浏览一下:

int totalPrice=1+2*3;

这是一个语句,它被分解为 操作 ,如下所示:

  • 2乘以3
  • 加1
  • 将结果设置到名为 totalPrice
  • 的局部变量中

此时要问的一个很好的问题是:

所以在Multiply 2 by 3运行之后,6存储在哪里?

答案:堆栈。稍后会详细介绍!

关于方法的快速补充说明

Hello("all!");这样的方法调用接受表达式作为参数。 Hello(1+1;); 失败,因为那是那里的声明。 Hello(1+(2*3)); 不过还好。

操作

每个操作接受任意数量的操作数,然后可选地输出一些东西。例如,乘法运算接受 2 个操作数 - A * B - 然后输出它们相乘后的结果。 new Something() 接受任意数量的构造函数参数并输出一个 reference 到新创建的对象。

这是重要的部分。

操作的输出总是进入堆栈。输入也总是先入栈。

那么这个堆栈是什么东西?

满满的stack machine is out of scope here - if you're interested in the full details then check out e.g. this wikipedia article。快速总结是所有基于 C 的语言都使用相同的概念。让我们重新审视上面的 C# 语句并在堆栈操作中描述它:

int totalPrice=1+2*3;
  • 将 2 压入堆栈

    堆栈现在只有 [2]

  • 将 3 压入堆栈

    堆栈现在是 [2,3]

  • 乘法(从堆栈中弹出两个值并将它们相乘)

    堆栈现在是 [6]

  • 将 1 压入堆栈

    堆栈现在是[6,1]

  • Add(从堆栈中弹出两个值,将它们相加)

    堆栈现在是 [7]

  • Store in local totalPrice(弹出栈值并存储)

    堆栈现在是空的

旁注:这些堆栈操作是 C# 编译器生成的。叫做CIL or .NET IL.

调用栈

健康警告!调用堆栈完全不同。是可以"overflow".

的调用栈

原来的说法呢?

好的,希望我们已经涵盖了足够多的内容,让这部分内容更有意义!那么我们就拿原文来说吧:

var dbMigrator = new DbMigrator(new Logger());

首先,new 操作就像任何其他方法调用一样 - 所有参数都被压入堆栈,方法被调用(将它们全部弹出堆栈)并且 return然后将值放入堆栈。

所以,这里描述的是堆栈样式:

  • 新建 Logger 对象(在堆上创建一个 Logger 对象并将对它的引用放在堆栈上。没有参数,所以它不会弹出任何东西)

    堆栈现在是[记录器参考]

  • 新 DBMigrator 对象(弹出 1 arg,推送引用)

    堆栈现在是 [DBMigrator 参考]

  • 存储在本地dbMigrator

    堆栈现在为空

要真正巩固这一点,请将其与替代方案(也有效)进行比较:

var logger = new Logger();
var dbMigrator = new DbMigrator(logger);
  • 新建记录器对象

    堆栈现在是[记录器参考]

  • 存储在本地logger

    堆栈现在为空

  • 加载本地logger

    堆栈现在是[记录器参考]

  • 新建 DbMigrator 对象

    堆栈现在是 [DBMigrator 参考]

  • 存储在本地dbMigrator

    堆栈现在是空的[=225=

您刚刚学到的内容: 它们都可以工作,但第二个稍微慢一些 - 它 更多 。看那些栈操作,好像在做一些完全没有意义的事情——就像你把东西放在文件柜里,然后马上又拿出来一样!

什么时候应该在堆栈上使用局部变量?

顺便提一句,有些语言没有本地语言,只使用堆栈。它们可以被极大地优化并且往往运行得更快,但它们也更难编写。当您需要多次引用某些内容时,本地人很棒:

Logger logger=new Logger();
// Using it twice!
logger.A();
logger.B();

如果您只使用过一次,那么这也完全有效:

(new Logger()).A();

奖金回合: 假设方法 A 确实 return this; 导致再次引用堆栈上的 Logger 对象。这会让我们这样做:

(new Logger()).A().B();

这是方法链接 - 它与函数式编程相关,并且由于它的紧凑性而变得越来越普遍。

总结

局部变量并不是唯一的存储方式——堆栈也是如此!您不需要跟踪堆栈 - 编译器会为您完成所有这些工作。你只需要考虑那些输入/输出值。