不可挽回地传递收藏品

时间:2012-08-17 20:29:05

标签: c#

我正在对一堆代码进行大量重构,这些代码过去常常使用一堆不断调整大小的多维数组。我创建了一个数据对象来替换2D数组,现在我正在传递一个这样的列表。

我发现了一些令我担忧的东西。假设我有一些看起来像这样的代码:

List<NCPoint> basePoints = new List<NCPoint>();

// ... snip populating basePoints with starting data

List<NCPoint> newPoints = TransformPoints(basePoints, 1, 2, 3);

public List<NCPoint> TransformPoints(List<NCPoint> points, int foo, int bar, int baz){
    foreach(NCPoint p in points){
        points.X += foo
        points.Y += bar
        points.Z += baz
    }

    return points;
}

我们的想法是保留原始点(basePoints)列表和更新点列表(newPoints)。但是C#通过引用传递列表,就像任何对象一样。这会更新basePoints,因此现在basePointsnewPoints都将拥有相同的数据。

目前,我正在努力制作一张传入的List的完整副本,然后才能查看数据。这是确保对函数内对象的更改在函数外部没有副作用的唯一合理方法吗?是否有类似于使用const传递对象的内容?

3 个答案:

答案 0 :(得分:3)

您可能正在搜索ReadOnlyCollection

  

提供通用只读集合的​​基类。

示例:

public IEnumerable<..> GetReadonlyCollection(List<...> originalList) 
{
  return new ReadOnlyCollection<string>(originalList);
}

请注意一个事实:这提供了使readonly(不可变)集合包含类型的服务。我可以获取一个对象frim该集合并更改它,如果该对象是引用类型,那些更改也会在原始集合中进行反射。

如果你想拥有只读对象,这会变得有点棘手(取决于你的对象有多复杂)。基本思想是(Servy也建议)使用 readonly 公共成员对原始对象进行包装(因此对于 类型的消费者而言它变得不可变了)。

希望这有帮助。

答案 1 :(得分:3)

简而言之:不。

C#本身没有const引用的概念。如果你想让一个对象不可变,你必须明确地编码它,或者利用其他“技巧”。

您可以通过多种方式使集合不可变(ReadOnlyColelction,返回迭代器,返回浅表副本)但保护序列,而不是内部存储的数据。

因此,您真正需要做的就是返回深层复制或投影,可能使用LINQ:

public IEnumerable<NCPoint> TransformPoints(List<NCPoint> points, int foo, int bar, int baz)
{
    // returning an iterator over the sequence so original list won't be changed
    // and creating new NCPoint using old NCPoint + modifications so old points
    // aren't altered.
    return points.Select(p => new NCPoint
        { 
           X = p.X + foo,
           Y = p.Y + bar,
           Z = p.Z + baz
        });
}

此外,返回迭代器的美妙之处(而不仅仅是将List<T>作为IEnumerable<T>返回,等等)是无法将其强制转换回原始集合类型。 / p>

更新:或者,用.NET 2.0说法:

public IEnumerable<NCPoint> TransformPoints(List<NCPoint> points, int foo, int bar, int baz)
{
    // returning an iterator over the sequence so original list won't be changed
    // and creating new NCPoint using old NCPoint + modifications so old points
    // aren't altered.
    NCPoint[] result = new NCPoint[points.Count];

    for (int i=0; i<points.Count; ++i)
    { 
        // if you have a "copy constructor", can use it here.
        result[i] = new NCPoint();
        result[i].X = points[i].X + foo;
        result[i].Y = points[i].Y + bar;
        result[i].Z = points[i].Z + baz;
    }

    return result;
}

关键是,有很多方法可以将某些东西视为不可变的,但我不会尝试在C#中实现C ++风格的“const correctness”,否则你会发疯的。当你想避免副作用等时,根据需要实施它。

答案 2 :(得分:0)

如果您使用的是C#4.5,我建议您查看Immutable Collections,可以通过Nuget包下载...

PM> Install-Package Microsoft.Bcl.Immutable

这篇博客文章很好地解释了如何使用它们: http://blogs.msdn.com/b/dotnet/archive/2013/09/25/immutable-collections-ready-for-prime-time.aspx

要保护附带的对象,您必须将所有属性设为私有。 我喜欢.With..(..)模式来帮助构建对象

    public class NCPoint
    {
        public int X { get; private set; }
        public int Y { get; private set; }
        public int Z { get; private set; }

        public NCPoint(int x, int y, int z)
        {
            this.X = x;
            this.Y = y;
            this.Z = z;
        }

        public NCPoint WithX (int x)
        {
            return x == X ? this : new NCPoint(x, Y, Z);
        }

        public NCPoint WithY(int y)
        {
            return y == Y ? this : new NCPoint(X, y, Z);
        }

        public NCPoint WithZ(int z)
        {
            return z == Z ? this : new NCPoint(X, Y, z);
        }
    }

你可以像这样使用它:

var p = points[i];
var newPoint = p.WithX(p.X + foo)
                .WithY(p.Y + bar)
                .WithZ(p.Z + baz);

我理解这个解决方案并不是每个人都喜欢的,因为它需要大量的编码。但我发现它非常优雅。

最后,调用不可变列表的替换方法:

immutablePoints.Replace (p, newPoint);