为什么结构中的这个值根本没有改变

本文关键字:改变 结构 为什么 | 更新日期: 2023-09-27 17:59:03

我相信这是一个非常简单的问题。有人能解释为什么这个代码输出1000,而不是1050 吗

public class Program
    {
        public static void Main()
        {
            Bus b = new Bus(1000);
            ((Car)b).IncreaseVolume(50);
            Console.WriteLine(b.GetVolume());
        }
    }
    public interface Car
    {
        int GetVolume();
        void IncreaseVolume(int amount);
    }
    public struct Bus : Car
    {
        private int volume;
        public Bus(int volume)
        {
            this.volume = volume;
        }
        public int GetVolume()
        {
            return volume;
        }
        public void IncreaseVolume(int amount)
        {
            volume += amount;
        }
    }
}

为什么结构中的这个值根本没有改变

将值类型(struct)强制转换为接口会将值框化。因此,您在值的装箱副本上调用该方法,而不是在值本身上调用。

值类型(struct)由值传递给,但接口被认为是引用类型(而不是值类型)。让我们看看:

Bus b = new Bus(1000);

现在,b包含卷设置为1000的Bus

Car c = (Car)b;

现在,b中的值被复制,并被制成Car的引用类型(装箱)。现在c包含一个指向装箱副本的指针。

c.IncreaseVolume(50);

在引用类型上,调用IncreaseVolume,它是Car接口的成员。它接收对装箱值的引用。它使用一个指向框中值的托管指针(使其再次成为值类型)。

void Car.IncreaseVolume(int amount)
{
    ((Bus)this).IncreaseVolume(amount);
}

现在,您的方法将作用于框中的值:

public void IncreaseVolume(int amount)
{
    volume += amount;
}

现在该方法返回。请注意,没有任何操作对b中的值起作用,只对其副本起作用。因此,下一条语句将打印1000:

Console.WriteLine(b.GetVolume());

就是这样。

尽管值类型实现可变接口有时很有用,但使用此类类型时必须格外小心。C#假装所有值类型都是对象,这在这方面有点无益(事实上,它们是可转换Object的东西),因为这意味着没有办法让编译器警告错误的使用。

除了完全忽略接口的选项外,通常有必要使用可变接口类型的代码必须做以下两件事之一:

  1. 将结构的实例强制转换为接口类型,再也不要将其强制转换为除接口类型"Object"或"ValueType"之外的任何类型。特别是,永远不要把它重新塑造成自己的类型。请注意,在不同的接口或引用类型之间进行强制转换是可以的,前提是实例永远不会被强制转换回自己的类型。这是像"列表"这样的东西最常见的模式。Enumerator",它通常被强制转换为"IEnumerator",而从不强制转换为其他任何值。
  2. 永远不要将结构用作接口类型,除非将其作为受约束的泛型"ref"参数传递给任何应该修改它的方法。这样做的代码通常在语法上"笨拙",但在语义上是正确的。结构赋值可以用来获取结构形状的快照,这一事实可能允许通过其他方式不容易或有效地获得语义,但有很多方法可以出错,这些错误不会导致编译器诊断,但不会产生正确的行为。

在大多数情况下,如果需要一些东西来实现一个可变的接口,那么有问题的类型应该是一个类。可突变结构通常只应通过直接修改底层字段或使用对ref传递的实例进行操作的静态方法来允许突变。