【问题标题】:How to have lists sharing a same struct instance?如何让列表共享相同的结构实例?
【发布时间】:2013-11-12 13:49:55
【问题描述】:

我有一个描述点的结构:

public struct Point
{
    int _x;
    int _y;

    #region properties
    public int X
    {
        get { return _x; }
        set { _x = value; }
    }
    public int Y
    {
        get { return _y; }
        set { _y = value; }
    }
    #endregion
}

很多点被实例化并存储在几个列表中。有时我必须更改一些点,因为要知道点存储在哪个列表中可能很棘手,我想创建一个包含所有点实例的列表,认为此列表中的更改会影响其他列表。

为了清楚下面的一些代码来解释我想要做什么:

List<Point> lstAllPoints = new List<Point>() { };
List<Point> lst1 = new List<Point>() { };
List<Point> lst2 = new List<Point>() { };

//some points are generated and stored in a list
for (int i = 0; i < 10; i++)
{
    Point pt = new Point();
    pt.X = i;
    pt.Y = i + 1;
    lstAllPoints.Add(pt);
    lst1.Add(pt);
}

//some other points are generated and stored in another list
for (int j = 0; j < 10; j++)
{
    Point pt = new Point();
    pt.X = j;
    pt.Y = j + 3;
    lstAllPoints.Add(pt);
    lst2.Add(pt);
}

现在我的想法是,如果我改变 lstAllPoints 中的一个点,那么其他列表中的对应点就会改变。

lstAllPoints[0].X = 400;
lstAllPoints[10].X = 800;

然后: lst1[0].X 等于 400,lst2[0].X 等于 800;

但由于 Point 在这种情况下是列表中的结构,因此重新初始化“lstAllPoints[0].X = 400”不起作用,并且因为结构是值类型,所以实例 lstAllPoints[0] 和 lst1[0] 不是同一实例: lstAllPoints 的更改不会影响其他列表中的点。

我使用的解决方法是将结构更改为类。 A Point 是一个引用类型,一切正常......完美。

我的问题是:将 struct 更改为 class 是唯一的解决方法吗?是否没有解决方案可以用相同结构实例的实例填充列表,从而更改列表中的点会更改共享同一实例的所有其他点?

我认为装箱/拆箱没有用。我想到了指针,但后来我必须在不安全的环境中工作,我不想这样做。我想过使用关键字 ref 但它只能应用于方法参数:

//someting like
List<ref Point>...

除了把struct改成class真的没有别的解决办法了吗?

提前致谢!

编辑 在这里进行编辑以回答您的一些 cmets。 我知道(或者我想我知道)使用 struct 的好处,特别是 struct 放在堆上。因为我可以有很多积分,所以考虑到性能问题,最好使用结构。 我不想使用指针,因为我不想设置不安全的上下文。 我没有任何理由不使用类。 尽管如此,我的问题更多的是针对我的文化,因为我很惊讶无法找到一种简单的方法来将单个结构实例存储在多个列表中。

谢谢大家

【问题讨论】:

  • 您找到的 3 个选项有什么问题?
  • 你为什么更喜欢结构而不是类?
  • 有什么理由将其保留为结构?
  • 如果你想对单个对象有多个引用,听起来你想要一个类。我看不出有任何理由不使用课程——这就是他们的目的! :)

标签: c# list struct instance


【解决方案1】:

您可以自己编写类到结构的包装器:)

using System;
using System.Collections.Generic;

namespace Test {
  public class Ptr<T> where T : struct {
    public T Value { get; set; }
  }

  class Program {
    static void Main(string[] args) {
      var a = new List<Ptr<int>>();
      var b = new List<Ptr<int>>();

      var ptr = new Ptr<int> { Value = 7 };
      a.Add(ptr);
      b.Add(ptr);

      a[0].Value = 3;
      Console.Out.WriteLine(b[0].Value);
    }
  }
}

【讨论】:

  • 请注意,这将比仅仅将底层类型设为类而不是结构来执行更糟糕。仅当类型几乎总是需要值语义并且仅偶尔需要引用语义时才应使用此选项。
  • @Servy,你的意思是如果创建了很多实例并且很少需要引用的需要,那么包装器解决方案将比结构更糟糕?
【解决方案2】:

如果你想将某物用作实体,它需要是类类型。我建议你应该做的是声明:

struct Point : IEquatable<Point> {
  public int X,Y;
  public Point(int x, int y) { X=x; Y=y; }
  public bool Equals(Point other) { return other.X==X && other.Y==Y; }
  public override bool Equals(Object obj) { return (obj is Point) && Equals((Point)Obj);
  public int GetHashCode() { return X*47 + Y; }
}
class MovableEntity 
{
  public Point Location;
  ExposedHolder(Point v) { Location = v; }
}

然后使用 Point 类型的变量来封装 X 和 Y 的行为独立于任何其他坐标,并使用 MovableEntity 类型的变量来封装引用一个实体(这样更改一个此类变量的 X 字段将更改另一个变量的 Y)。

使用如图所示定义的类型,访问 MovableEntity 的 Location 字段中的 X 和 Y 字段将产生与访问可变 Point 类类型中的此类字段基本相同的性能,但也允许能够说出以下内容:

MovableEntity thing1, thing2;
...
thing1.Location = thing2.Location;

这将具有将thing1的位置设置为与thing2的当前位置匹配的效果,而不会干扰thing1或thing2的身份。如果 MovableEntity 直接封装了 X 和 Y,而不是使用暴露字段结构来这样做,则有必要改为这样说:

thing1.X = thing2.X;
thing2.Y = thing2.Y;

此外,如图所示定义事物使得拥有Dictionary&lt;Point, somethingElse&gt; 成为可能,这将保护用作密钥的任何点免受不当突变。

请注意,我使用了公共字段而不是属性。如果一个结构的全部目的是封装一组相关但独立的变量,那么自动属性将降低性能而没有提供任何真正的好处。此外,与此类类型的字段相比,公开结构类型的自动属性效率低得多且难以使用,因此应避免使用,除非它们提供足以证明成本合理的语义优势。 p>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-23
    • 2016-02-21
    • 2020-05-22
    • 1970-01-01
    • 2014-06-14
    相关资源
    最近更新 更多