6

我正在对过去使用一堆不断调整大小的多维数组的一堆代码进行大重构。我创建了一个数据对象来替换 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吗?

4

3 回答 3

3

您可能正在搜索 ReadOnlyCollection

为通用只读集合提供基类。

例子:

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

请注意一个事实:这是为使只读(不可变)集合而不包含类型提供服务。我可以从该集合中获取一个对象并对其进行更改,如果该对象是引用类型,那么这些更改也会在原始集合中反映出来。

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

希望这可以帮助。

于 2012-08-17T20:29:52.690 回答
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
        });
}

此外,返回迭代器(而不是仅仅将 aList<T>作为 aIEnumerable<T>等返回)的美妙之处在于它不能被强制转换回原始集合类型。

更新:或者,用 .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 正确性”,否则你会发疯的。当您想避免副作用等时,根据需要实施它。

于 2012-08-17T20:34:06.727 回答
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);

于 2014-03-17T15:56:07.610 回答