【问题标题】:Should I be reusing collections passed as parameters我是否应该重用作为参数传递的集合
【发布时间】:2011-06-29 19:33:22
【问题描述】:

昨天我花了一些时间试图找到一个错误。长话短说,我终于意识到是因为这个构造函数:

public Triangle(List<Vertex> vertices) {
    this._values = vertices;
}

我试图用一个值列表初始化一个对象,而该对象只是引用了我的对象,而不是从列表中获取值。如果我不放弃作为参数传递的列表并稍后将其用于其他用途,例如用相同的值初始化其他内容,或者如果我决定清除它并填充新值,我显然会破坏我的 @ 状态987654322@对象不知情。

我的第一反应是在构造函数中“修复错误”,但随后我开始思考它是否真的应该是这样。涵盖此类事情的良好做法是什么?一般来说,我应该如何看待采用值列表的构造函数/初始化方法?他们应该保持原样吗?我是否可以重复使用列表,当它导致错误时是谁的错?

我的意思是,我显然可以这样做:

var triangle = new Triangle(new List<Vertex>(vertices));

但它不应该由 Triangle 类的创建者完成吗?

我想知道这方面的一些指导方针。谢谢。

【问题讨论】:

  • @Timwi:好的,我修复了错误。我真的认为当实际代码无关紧要时就是这种情况,因为我不问这个具体情况,而是问事情一般应该如何,这只是一个例子

标签: c# .net reference parameter-passing


【解决方案1】:

是的,接收类(三角形)应该复制一份,除非设计是有意共享列表。

分享可能有用,但这是个例外。我不认为三角形想要与其他东西共享它的顶点列表。

请注意,它仍然可以共享顶点(元素)。

【讨论】:

    【解决方案2】:

    我个人同意 Henk 的观点;你应该创建一个副本。

        /// <summary>
        /// Initialises a new instance of the <see cref="Triangle"/> class that 
        /// contains elements copied from the specified collection.
        /// </summary>
        /// <param name="vertices">
        /// The collection of vertices whose elements are to be copied.
        /// </param>
        public Triangle(IEnumerable<Vertex> vertices)
        {
            this.Vertices = new List<Vertex>(vertices);
        }
    

    无论您选择什么,只要确保您记录下来,以便消费者知道会发生什么行为。

    因此消费者知道他们可以安全地拨打new Triangle(vertices)

    【讨论】:

      【解决方案3】:

      C# 是按值传递的语言,但由于列表是引用类型,它按值传递其引用。正如您所说,这意味着您将列表的共享引用传递给您的类的构造函数。在代码中的任何地方进行的修改都会影响同一个列表。

      这取决于你的班级的期望行为,什么是适当的行动。如果要进行深拷贝,最简单的方法是在构造函数中分配一个新列表,并将 IEnumerable 引用传递给列表的构造函数。

      如果您想共享参考,这是一个完全有效的解决方案,只需确保正确记录您的课程(或命名您的课程)。

      【讨论】:

        【解决方案4】:

        在这种情况下,将 List 对象传递给构造函数将被视为糟糕的设计。也许更好的解决方案是使用一种方法

        class Triangle
        {
            List<Vertex> Vertices = new List<Vertex>(); // The triangle owns the vertex collection...
        
            public void SetVertices(IEnumerable<Vertex> vertices)
            {
                this.Vertices.Clear();
        
                this.Vertices.AddRange(vertices);
            }
        }
        

        【讨论】:

        • 因为它会引发诸如 OP 所描述的问题。 Triangle 类应该拥有它使用的集合的所有权。此外,它不需要列表的附加功能。在这种情况下,最低要求的功能是 Triangle 可以枚举顶点集合,因此 IEnumerable 更具凝聚力,并且不会与任何特定的集合实现紧密耦合。
        【解决方案5】:

        我会说这是一个文档问题。文档,即使只是智能感知文档,也应该说明该类是使用给定列表中的 初始化的,还是直接使用给定列表。给定任何可变引用类型,这是一个有效的问题,应该记录在案。

        由于缺乏适当的文档,我想说这取决于您,该课程的消费者,来保护自己免受无证行为的侵害。你有两个选择:

        1. 自己找出文档应该告诉您的内容。您可以使用 Reflector 或简单的实验来确定代码对传递给它的可变对象做了什么。

        2. 保护自己免受班级行为的影响,无论它是什么。如果一个类接受一个可变对象,请不要重用该对象。这样,即使以后类的行为发生变化,您也是安全的。

        在您的具体情况下,我不认为 Triangle 类是错误的。它的构造函数可以采用IEnumerable&lt;Vertex&gt;1 并使用这些值初始化成员List&lt;Vertex&gt;,但它的设计者却选择直接采用List&lt;Vertex&gt; 并使用那。这可能是一个基于绩效的决定。

        1 完整地说,如果有点迂腐,我应该提一下,即使使用IEnumerable&lt;Vertex&gt;,您仍然可能遇到同样的问题。该类仍然可以存储和重用对该对象的引用,因此对以后对列表所做的更改很敏感。然而,在这种情况下,我认为Triangle 类被破坏了。约定规定,除了少数例外,采用IEnumerable 的方法或构造函数将使用一次然后丢弃它。

        【讨论】:

        • 这是采取安全行动的好方法,谢谢
        【解决方案6】:

        您需要的是列表的克隆或深层副本。

        请参考cloning a list这个答案

        this 了解更多关于深拷贝的信息

        【讨论】:

        • 克隆列表不是问题,我只是想知道谁应该这样做
        猜你喜欢
        • 1970-01-01
        • 2013-11-23
        • 2011-09-13
        • 2016-01-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-26
        • 1970-01-01
        相关资源
        最近更新 更多