当前位置: 首页 > 知识库问答 >
问题:

在对象构造过程中,构造函数的用例与初始化设置值的用例之间有什么区别?

楚弘益
2023-03-14

仅初始化设置程序仅在对象构造期间向属性或索引器元素赋值。在对象构造过程中,构造函数的用例与初始化设置值的用例之间有什么区别?

样本1:

public class Person
{
   private string _myName;

   public Person(string myName)
   {
      _myName= myName;
   }

   public string Name => _myName;
}

样本2:

public class Person
{
   private string _myName;

   public string Name
     {
         get { _myName; }
         init { _myName= value; }
     }
}

示例3(忽略此示例,因为它与示例2相同):

public class Person
{
   private string _myName;

   public string Name
     {
         get => _myName; 
         init => _myName= value;
     }
}

共有3个答案

单于皓轩
2023-03-14

其思想是允许您知道的只读属性仅在对象初始化期间具有写入值,但在初始化之后无法写入该值。

如果定义了set方法而不是init,则可以在对象生命周期内的任何时间写入该值。

这允许您初始化属性,而无需将其作为参数直接传递给构造函数。

例如:

class A
{
    public int X { get; init;} 
} 

允许这样做:

A a = new A() 
{
    X = 3,
};

然后尝试写入X,将无法编译:

a.X = 5;

给出一个编译错误:

CS8852  Init-only property or indexer 'A.X' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

如果不使用init,而是使用set,则可以随时编写对象。

如果您定义一个没有init方法的只读属性,您仍然可以在构造函数中初始化该属性,但是您必须向构造函数传递一个参数。因此,使用init的决定更多地取决于您的代码样式,以及您将初始化字段的参数保存在何处,以及您是否愿意将其传递给构造函数。

请注意,这与字段访问无关。如果init定义为public,则可以从类外部对其进行初始化。它也可以声明为privateprotected,然后只能分别从类或派生类访问它。

贺宏逸
2023-03-14

让我们从分析init属性开始。

举两个简单的例子:

public class Foo
{
    public string Name { get; set; }
}

public class Bar
{
    public string Name { get; init; }
}

让我们看看两者之间的IL差异:

.class nested public auto ansi beforefieldinit Foo extends [System.Runtime]System.Object
{
    .method public hidebysig specialname instance string get_Name () cil managed { ... }
    .method public hidebysig specialname instance void set_Name (string 'value') cil managed { ... }

    .property instance string Name()
    {
        .get instance string Foo::get_Name()
        .set instance void Foo::set_Name(string)
    }
}

.class nested public auto ansi beforefieldinit Bar extends [System.Runtime]System.Object
{
    .method public hidebysig specialname instance string get_Name () cil managed { ... }
    .method public hidebysig specialname instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_Name (string 'value') cil managed { ... }

    .property instance string Name()
    {
        .get instance string Bar::get_Name()
        .set instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) Bar::set_Name(string)
    }
}

两者之间唯一的区别是modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit)。

FooBar都实现了一个publicgetter

差异是由编译器控制的。只能在构造期间或在对象初始值设定项内设置init属性。

让我们看一个更复杂的例子:

public class Electricity
{
    private const double _amps = 4.2;
    public double Amps => _amps;
    
    private readonly double _power;
    public double Power => _power;
    
    private double _volts;
    public double Volts
    {
        get => _volts;
        init
        {
            _volts = value * 2;
            _power = value * _amps;
            //_amps = 99.0; // Not allowed! Can only be set by direct assignment - effectively prior to construction!
        }
    }
    
    public Electricity(double volts)
    {
        this.Volts = volts * 5;
        _power = 9.2;
        //_amps = 42.0; // Not allowed! Can only be set by direct assignment - effectively prior to construction!
    }
    
    public void Danger()
    {
        //_amps = 0.0; // Not allowed! Can only be set by direct assignment - effectively prior to construction!
        //_power = 0.0; // Not allowed! Can only be set in constructor or init
        //this.Volts = -1.0; // Not allowed! Can only be set in constructor or init
        _volts = 0.0; // Allowed!
    }
}

有了它,我可以用以下代码创建一个实例:

Electricity e = new Electricity(1.0) { Volts = 2.0 };

现在,这有效地调用new Electric(1.0)来创建类的实例,并且,由于它是唯一的构造函数,我被迫使用volts的参数调用它。注意,在构造函数内部,我可以调用this。伏特=伏特*5;

在分配给e之前,调用初始值设定项块中的代码。它只是将2.0分配给Volts——它是e.Volts=2.0的直接等价物 如果我们没有用init设置器声明Volts

结果是,当e被分配时,构造函数和对setVolts的调用都已经完成。

现在,让我们试着让这个代码更加健壮。假设我希望能够设置任意两个属性,并让代码计算第三个属性。

一个幼稚和不正确的实施是:

public class Electricity
{
    public double Volts { get; private set; }
    public double Amps { get; private set; }
    public double Watts => this.Volts * this.Amps;

    public Electricity(double volts, double amps)
    {
        this.Volts = volts;
        this.Amps = amps;
    }

    public Electricity(double volts, double watts)
    {
        this.Volts = volts;
        this.Amps = watts / this.Volts;
    }

    public Electricity(double amps, double watts)
    {
        this.Amps = amps;
        this.Volts = watts / this.Amps;
    }
}

但是,当然,这不能编译,因为三个构造函数签名是相同的。

但是我们可以使用init来创建一个对象,无论我设置了什么属性(下面示例中的Watts除外)。

public class Electricity
{
    private readonly double? _volts = null;
    private readonly double? _amps = null;
    private readonly double? _watts = null;

    public double Volts
    {
        get => _volts ?? 0.0;
        init
        {
            _volts = value;
            if (_amps.HasValue)
            {
                _watts = _volts * _amps;
            }
            else if (_watts.HasValue)
            {
                _amps = _watts / _volts;
            }
        }
    }

    public double Amps
    {
        get => _amps ?? 0.0;
        init
        {
            _amps = value;
            if (_volts.HasValue)
            {
                _watts = _volts * _amps;
            }
            else if (_watts.HasValue)
            {
                _volts = _watts / _amps;
            }
        }
    }

    public double Watts
    {
        get => _watts ?? 0.0;
        init
        {
            _watts = value;
            if (_volts.HasValue)
            {
                _amps = _watts / _volts;
            }
            else if (_amps.HasValue)
            {
                _volts = _watts / _amps;
            }
        }
    }
}

我现在可以这样做:

var electricities = new[]
{
    new Electricity() { Amps = 2.0, Volts = 3.0 },
    new Electricity() { Watts = 2.0, Volts = 3.0 },
    new Electricity() { Amps = 2.0, Watts = 3.0 },
    new Electricity(),
};

这给了我:

因此,最终结果是构造函数是强制性的,但是init属性是可选的,但是必须在构造时使用,然后才能将任何引用传递回调用代码。

谢烨烨
2023-03-14

本表描述了主要的区别和相似之处:(包括我的编辑…)

 类似资料:
  • 问题内容: 只是想知道编译这样的代码的原因: 此代码与构造函数中的代码有什么区别?此代码 在 创建对象 之前 执行。 问题答案: 花括号内没有名称的代码将成为类构造函数的一部分,并在类构造函数所包含的逻辑之前执行。 快速示例:

  • 映射对构造函数没有限制或要求 (__init__ )类的方法。您可以自由地为您想要的函数要求任何参数,为ORM未知的实例分配属性,并且通常在编写Python类的构造函数时做您通常会做的任何其他事情。 sqlAlchemy ORM不调用 __init__ 从数据库行重新创建对象时。ORM的过程有点类似于Python标准库的 pickle 模块,调用低级 __new__ 方法,然后在实例上悄悄地恢复属

  • 问题内容: 我有以下这段代码: 现在在控制台中,构造函数要在的构造函数之前执行。 我想知道为什么会这样。 问题答案: 这是因为在编译时,编译器会将在声明位置完成的每个初始化都移到类的每个构造函数中。因此,类的构造函数可以有效地编译为: 因此,显然构造函数是在类的语句之前执行的。同样,如果您的类中还有其他构造函数,则初始化将移至所有它们。 让我们看一个简单类的字节码: 编译该类,并执行命令- 。您将

  • 生成类对象时,其成员可以用类的构造函数初始化。构造函数是与类同名的成员函数。程序员提供的构造函数在每次生成类对象(实例化)时自动调用。构造函数可以重载.提供初始化类对象的不同方法。数据成员应在类的构造函数中初始化或在生成对象之后设置其数值。 常见编程错误 6.7 类的数据成员只能在类定义中初始化。 常见编程错误 6.8 试图声明构造函数的返回类型和返回植是个语法错误。 编程技巧 6.5 适当时候(

  • 问题内容: 在Java中,但是在其他OO语言中,初始化属性定义之间也有区别,例如 并使用构造函数对其进行初始化? 我想不出任何实际的区别,有没有?否则,即使结果相同,是否存在一种方法优于另一种方法的情况? 问题答案: 初始化顺序在这里很重要。 将字段设置为默认初始值(0,false,null) 调用对象的构造函数(但不要执行构造函数的主体) 调用超类的构造函数 使用初始化程序和初始化块初始化字段