是否使用其他列表复制值或引用初始化通用列表?

时间:2016-06-30 21:53:57

标签: c# list

如果我有List<T> Foo并通过将List<T> Bar传递给Foo的构造函数来初始化另一个Bar,那么Bar是否可以访问原始对象在Foo?或Bar中的对象是否单独复制?

这是一个愚蠢的例子:

class Car
{
    public string Make   { get; private set; }
    public string Model  { get; private set; }
    public string Year   { get; private set; }

    public int FuelLevel { get; private set; } = 0;
    public int OilLevel  { get; private set; } = 0;

    public Car(string make, string model, string year)
    {
        Make = make;
        Model = model;
        Year = year;
    }

    public void Refuel()
    {
        FuelLevel = 100;
    }
}

class Program
{
    public static void Main(string[] args)
    {
        List<Car> CarsThatJoeOwns = new List<Car> { new Car("Ford", "Explorer", "2005"),
                                                    new Car("Hyundai", "Elantra", "2011") };

        // For some reason, Paul owns the exact same types of cars that Joe owns...
        List<Car> CarsThatPaulOwns = new List<Car> (CarsThatJoeOwns);

        foreach (Car car in CarsThatPaulOwns)
        {
            car.Refuel(); // <---- does this affect the cars that Joe owns too?
        }
    }
}

3 个答案:

答案 0 :(得分:2)

这是List<T>

的构造函数
    public List(IEnumerable<T> collection) {
        if (collection==null)
            ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
        Contract.EndContractBlock();

        ICollection<T> c = collection as ICollection<T>;
        if( c != null) {
            int count = c.Count;
            if (count == 0)
            {
                _items = _emptyArray;
            }
            else {
                _items = new T[count];
                c.CopyTo(_items, 0);
                _size = count;
            }
        }    
        else {                
            _size = 0;
            _items = _emptyArray;
            // This enumerable could be empty.  Let Add allocate a new array, if needed.
            // Note it will also go to _defaultCapacity first, not 1, then 2, etc.

            using(IEnumerator<T> en = collection.GetEnumerator()) {
                while(en.MoveNext()) {
                    Add(en.Current);                                    
                }
            }
        }
    }

如果集合可以是ICollection<T>,那么它将被复制到新数组。

如果不是,它会执行Add

    public void Add(T item) {
        if (_size == _items.Length) EnsureCapacity(_size + 1);
        _items[_size++] = item;
        _version++;
    }

两个实例看起来都在复制项目的引用。但是,传递给构造函数的数组(a.k.a Foo)已丢失其引用。这意味着,对第一个列表的任何更新都不会更新传入的列表。

答案 1 :(得分:1)

是。在您的情况下,将参考现有的Car对象初始化新列表。所以调用Refuel函数将导致Paul和Joe汽车被修改(因为它们是同一个对象)。

答案 2 :(得分:1)

Car是引用类型,因此会发生以下情况:

var car = new Car("Ford", "Explorer", "2005");
var carReference = car;
carReference.Refuel();

//Will have the value of 100, even if no method was called in the car
//object, but because it is a reference type, calling Refuel method
//on carReference will also affect to the variable referenced by
//carReference (car)
var fuelLevel = car.FuelLevel

在您的代码示例中,您有以下特定部分:

List<Car> CarsThatJoeOwns = new List<Car>
{
    new Car("Ford", "Explorer", "2005"),
    new Car("Hyundai", "Elantra", "2011")
};

// For some reason, Paul owns the exact same types of cars that Joe owns...
List<Car> CarsThatPaulOwns = new List<Car> (CarsThatJoeOwns);

分配List<T>时调用的CarsThatPaulOwns构造函数将现有列表CarsThatJoeOwns作为参数,实现接口ICollection<T>,因此CarsThatJoeOwns可以投放到ICollection<T>

另外,看一下通用List类的构造函数的源代码:

public List(IEnumerable<T> collection) {
    if (collection==null)
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
    Contract.EndContractBlock();

    ICollection<T> c = collection as ICollection<T>;
    if( c != null) {
        int count = c.Count;
        if (count == 0)
        {
            _items = _emptyArray;
        }
        else {
            _items = new T[count];
            c.CopyTo(_items, 0);
            _size = count;
        }
    }    
    else {                
        _size = 0;
        _items = _emptyArray;
        // This enumerable could be empty.  Let Add allocate a new array, if needed.
        // Note it will also go to _defaultCapacity first, not 1, then 2, etc.

        using(IEnumerator<T> en = collection.GetEnumerator()) {
            while(en.MoveNext()) {
                Add(en.Current);                                    
            }
        }
    }
}

由于CarsThatJoeOwns可以投放到ICollection<T>,因此会执行第c.CopyTo(_items, 0);行,并执行以下操作(如Microsoft参考源中所示):

public void CopyTo(T[] array, int arrayIndex) {
    // Delegate rest of error checking to Array.Copy.
    Array.Copy(_items, 0, array, arrayIndex, _size);
}

并且,如MSDN文档中所述,Array.Copy将执行以下操作:

  

... 备注 ...如果sourceArray和destinationArray都是   引用类型数组或者都是Object类型的数组,浅层   执行复制。数组的浅表副本是一个新数组   包含与原始Array相同元素的引用。该   元素本身或元素引用的任何东西都不是   复制。相反,Array的深层副本会复制元素和   由元素直接或间接引用的所有内容。

请注意“数组的浅表副本是一个新数组,其中包含对与原始数组相同的元素的引用”部分。简而言之,新列表CarsThatPaulOwns将保存对CarsThatJoeOwns列表中已存在的对象的引用,以及{:1}}循环定义:

foreach

也会影响foreach (Car car in CarsThatPaulOwns) { car.Refuel(); } 列表中的值。反之亦然的情况也是如此(在CarsThatJoeOwns列表上调用加油方法也会影响CarsThatJoeOwns列表。)