构造函数中的 C# 赋值给成员......不会改变它

Posted

技术标签:

【中文标题】构造函数中的 C# 赋值给成员......不会改变它【英文标题】:C# assignment in constructor to member ... doesn't change it 【发布时间】:2019-07-20 09:27:07 【问题描述】:

我有一个简单的类,用于存储缩放的整数值 使用带有“scale_factor”的成员变量“scaled_value”(long)。

我有一个用小数填充新类实例的构造函数 值(尽管我认为值类型无关紧要)。 分配给“scaled_value”插槽似乎......不会发生。 我已经插入了常量 1 的显式赋值给它。 下面的 Debug.Assert 失败...并且 scaled_value 为零。

在即时窗口中的断言中断时,我可以使用 assignment/inspect "scale_factor" 进行检查/设置;它会随着我的设置而改变。

我可以检查“scaled_value”。它始终为零。我可以输入 立即窗口执行的赋值给它,但它的值 不变。

我正在使用 Visual Studio 2017 和 C# 2017。

这个插槽有什么魔力?

public class ScaledLong : Base // handles scaled-by-power-of-ten long numbers
                  // intended to support equivalent of fast decimal arithmetic while hiding scale factors from user
   
  public long scaled_value; // up to log10_MaxLong digits of decimal precision
  public sbyte scale_factor;  // power of ten representing location of decimal point range -21..+21.  Set by constructor AND NEVER CHANGED.
  public byte byte_size;  // holds size of value in underlying memory array
  string format_string;

  <other constructors with same arguments except last value type>

  public ScaledLong(sbyte sf, byte size, string format, decimal initial_value)
  
     scale_factor = sf;
     byte_size = size;
     format_string = format;

     decimal temp;
     sbyte exponent;
       // rip exponent out of decimal value leaving behind an integer;
    _decimal_structure.value = initial_value;
    exponent = (sbyte)_decimal_structure.Exponent;
    _decimal_structure.Exponent = 0;  // now decimal value is integral
    temp = _decimal_structure.value;
     
     sbyte sfDelta = (sbyte)(sf - exponent);
     if (sfDelta >= 0)
       // sfDelta > 0
    this.scaled_value = 1;
    Debug.Assert(scaled_value == 1);
    scaled_value = (long)Math.Truncate(temp * DecimalTenToPower[sfDelta]);
     
     else
     
    temp = Math.Truncate(temp / DecimalHalfTenToPower[-sfDelta]);
    temp += (temp % 2); /// this can overflow for value at very top of range, not worth fixing; note: this works for both + and- numbers (?)
    scaled_value = (long)(temp / 2); // final result
     

  

【问题讨论】:

上面的代码中没有定义一些关键变量:_decimal_structureDecimalTenToPower 能否附上minimal reproducible example?上面的代码缺少各种类型,无法编译(和运行)。 Debug.Assert(this.scaled_value == 1); 的行为是否相同? 我在处理这些场景时的建议是将字段更改为具有支持字段的属性。在属性设置器上放置一个断点。调试代码。总是有一些代码调用了你没有预料到的 setter。 @mjwills:你的断点属性设置器的方案会帮助我找到这个。好主意。 【参考方案1】:

最大的谜题往往有着最愚蠢的基础。这是关于意外副作用的一课。

我通过思考发现了这一点,想知道究竟如何以意想不到的方式修改成员。我在阅读@mjwills 评论之前找到了解决方案,但他肯定在嗅探正确的东西。

我遗漏的(当然!)是我刚刚为该类编写了一个 ToString() 方法......它没有被调试。为什么我把它遗漏了?因为它显然不会影响任何事情,所以它不会成为问题的一部分。

嘶嘶!它使用成员变量作为暂存器并将其归零(有副作用);这显然是无意的。

这意味着当代码刚刚运行时,不会调用 ToString() 并且成员变量确实被正确修改。 (我什至对“Set”例程进行了单元测试,检查了所有这些,它们都在工作)。

但是,当您调试时......调试器可以(并且在这种情况下确实)显示局部变量。为此,它显然会调用 ToString() 来获得一个不错的可显示值。因此,单步操作导致 ToSTring() 被调用,并且其错误的临时变量分配在每次调用后将槽清零。

所以咬我的不是二传手。这可以说是一个吸气剂。 (需要时 FORTRAN 的 PURE 关键字在哪里?)

爱因斯坦讨厌远处的怪异行为。程序员讨厌远处令人毛骨悚然的副作用。

对于调试器在一个类上调用 ToString() 的想法有些奇怪,而该类的构造函数还没有完成。鉴于构造函数没有完成,ToString 可以信任哪些关于类状态的断言?我认为应该修复 MS 调试器。有了这个,我会花时间调试 ToString 而不是追逐它。

感谢您接受我的问题。它让我找到了答案。

【讨论】:

很高兴您得到了答案。通常一个很好的经验法则是不要让query 方法在您的代码中引入任何side-effectsstate changes。而是让command/mutating 方法执行必要的state changes。 martinfowler.com/bliki/CommandQuerySeparation.html 显然不打算让 ToString 发生变异。我有点惊讶 MS 没有对 ToString 和 Get 过程进行某种副作用分析以验证它不会改变拥有它的类的状态。很明显,这样的变化不会有好的结局。

以上是关于构造函数中的 C# 赋值给成员......不会改变它的主要内容,如果未能解决你的问题,请参考以下文章

c#中泛型类构造函数重载赋值时为啥不接受null?对其赋空值应给怎么做?

final总结

C++中,类内的成员变量自动初始化为零吗,而全局变量随意赋值

类和结构体的区别

给成员变量赋值的两种方法

复制构造函数中的c ++用户定义成员