c# - 使用副本更改对象仍会导致源对象更改
问题描述
我有一个点云对象
public interface IPointCloud
{
void ShiftToOrigin();
Vertex[] Vertices { get; set; }
// More here...
}
public class PointCloud : IEquatable<PointCloud>, IPointCloud
{
public PointCloud()
{
Id = new TId();
}
public PointCloud(IPointCloud pointCloud)
: this(pointCloud.Vertices, pointCloud.Index, pointCloud.Source) { }
public PointCloud(Vertex[] vertices) : this()
{
Vertices = vertices;
}
public PointCloud(Vertex[] vertices, int? index, string source = default)
: this(vertices)
{
Index = index;
Source = source;
}
#region Methods.
public Vector3 GetCentroid()
{
var centroid = new Vector3();
if (IsEmpty)
return centroid;
foreach (var vertex in Vertices)
centroid += vertex.Point;
return centroid / Vertices.Length;
}
public int ReorientateNormals()
{
if (!ContainsNormals)
return 0;
int counter = 0;
var centroid = GetCentroid();
for (int i = 0; i < Vertices.Length; ++i)
{
var normal = Vertices[i].Normal;
Vertices[i].Normal = FlipNormalTowardCentroid(Vertices[i], centroid);
if (normal != Vertices[i].Normal)
counter++;
}
return counter;
}
private Vector3 FlipNormalTowardCentroid(Vertex vertex, Vector3 centroid)
{
var normal = vertex.Normal.ToVector();
var viewPointVector = (centroid - vertex.Point).ToVector();
if (viewPointVector.DotProduct(normal) < 0)
normal *= -1;
return normal.ToVector3();
}
/// <summary>
/// Translate point cloud so that center = origin total shift applied to this point cloud is stored in this->shift.
/// </summary>
public void ShiftToOrigin()
{
var (minimumBound, maximumBound) = GetBoundingBoxAsPoints();
Vector3 newshift = Vector3.Divide(minimumBound + maximumBound, 2.0f);
for (int i = 0; i < Vertices.Length; ++i)
Vertices[i].Point = Vertices[i].Point - newshift;
GlobalShiftVector += newshift;
}
public virtual void RemovePoints(IPointCloud pointCloudToRemove)
{
if (pointCloudToRemove.Vertices == null || pointCloudToRemove.Vertices.Length == 0)
return;
var verticesToKeep = new List<Vertex>();
var verticesToRemove = pointCloudToRemove.Vertices.ToList();
foreach(var vertex in Vertices)
{
if (!verticesToRemove.Contains(vertex))
verticesToKeep.Add(vertex);
}
Debug.Assert(verticesToKeep.Count == Vertices.Length - verticesToRemove.Count);
Array.Clear(Vertices, 0, Vertices.Length);
Vertices = verticesToKeep.ToArray();
}
public IPointCloud Merge(IPointCloud pointCloud)
{
if (pointCloud == null || pointCloud.Vertices.Length == 0)
return this;
List<Vertex> vertices = pointCloud.Vertices.ToList();
if (Vertices == null || Vertices.Length == 0)
return new PointCloud(vertices.ToArray(), pointCloud.Index, pointCloud.Source);
vertices.AddRange(Vertices);
return new PointCloud(vertices.ToArray(), Index, Source);
}
public (Vector3 MinimumBound, Vector3 MaximumBound) GetBoundingBoxAsPoints()
{
var min = new Vector3();
var max = new Vector3();
if (Vertices.Length > 0)
{
foreach (var point in Vertices.Select(v => v.Point))
{
if (min.X > point.X)
min.X = point.X;
if (min.Y > point.Y)
min.Y = point.Y;
if (min.Z > point.Z)
min.Z = point.Z;
if (max.X < point.X)
max.X = point.X;
if (max.Y < point.Y)
max.Y = point.Y;
if (max.Z < point.Z)
max.Z = point.Z;
}
}
return (min, max);
}
public BoundingBox GetBoundingBox()
{
var (MinimumBound, MaximumBound) = GetBoundingBoxAsPoints();
return new BoundingBox(
new Range(MinimumBound.X, MaximumBound.X),
new Range(MinimumBound.Y, MaximumBound.Y),
new Range(MinimumBound.Z, MaximumBound.Z));
}
public double GetPointDensity()
{
var boundingBox = GetBoundingBox();
var volume = boundingBox.XRange.Length * boundingBox.YRange.Length * boundingBox.ZRange.Length;
return Vertices.Length / volume;
}
#endregion // Methods.
#region Operator Overrides.
public bool Equals([AllowNull] PointCloud other)
{
return other.Id == Id;
}
public static bool operator ==(PointCloud left, PointCloud right)
{
if (ReferenceEquals(left, null))
return ReferenceEquals(right, null);
return left.Equals(right);
}
public static bool operator !=(PointCloud left, PointCloud right)
{
if (ReferenceEquals(left, null))
return !ReferenceEquals(right, null);
return !left.Equals(right);
}
#endregion // Operator Overrides.
#region Object Overrides.
/// <summary>
/// Determines whether the specified <see cref="Range"/> is equal to the current one.
/// </summary>
/// <param name="obj">The <see cref="Range"/> object to compare with the current one.</param>
/// <returns><c>true</c> if the specified <see cref="Range"/> is equal to the current one;
/// otherwise <c>false</c>.</returns>
public override bool Equals(object obj)
{
return this.Equals(obj as PointCloud);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>A hash code for the current <see cref="Range"/>.</returns>
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 29 + Id.GetHashCode();
return hash;
}
}
public override string ToString()
{
StringBuilder builder = new StringBuilder($"Point Cloud {{{Id}}}, Index {Index ?? -1:N0} (\"{Source}\"), ");
builder.Append($"\tVertex count {Vertices.Length:N0}, Contains Normals = {ContainsNormals.ToString()}");
return builder.ToString();
}
#endregion // Object Overrides.
public TId Id { get; private set; }
public Vertex[] Vertices { get; set; }
public int? Index { get; set; }
public string Source { get; set; }
public bool IsEmpty => Vertices == null || Vertices.Length == 0;
public bool ContainsNormals => Vertices == null ? false : Vertices.Any(v => v.Normal.X != 0.0f || v.Normal.Y != 0.0f || v.Normal.Z != 0.0f);
public Vector3 GlobalShiftVector { get; private set; }
}
在哪里
public class Vertex : IEquatable<Vertex>, ICloneable
{
public Vertex() { }
public Vertex(Vector3 point, Vector3 normal)
{
Point = point;
Normal = normal;
}
public Vertex(Vector3 point) : this(point, new Vector3()) { }
#region IClonable.
public object Clone()
{
return (Vertex)MemberwiseClone();
}
#endregion // IClonable.
#region Object Overrides.
public override bool Equals(object obj)
{
return this.Equals(obj as Vertex);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 29 + Point.GetHashCode();
hash = hash * 29 + Normal.GetHashCode();
return hash;
}
}
#endregion // Object Overrides.
#region Comparison Operators.
public static bool operator ==(Vertex left, Vertex right)
{
if (ReferenceEquals(left, null))
return ReferenceEquals(right, null);
return left.Equals(right);
}
public static bool operator !=(Vertex left, Vertex right)
{
if (ReferenceEquals(left, null))
return !ReferenceEquals(right, null);
return !left.Equals(right);
}
#endregion // Comparison Operators.
#region IEquatable<Vertex>.
public bool Equals(Vertex other)
{
if (ReferenceEquals(other, null))
return false;
if (ReferenceEquals(other, this))
return true;
return Point.IsEqual(other.Point) &&
Normal.IsEqual(other.Normal);
}
#endregion // IEquatable<Vertex>.
public Vector3 Point;
public Vector3 Normal;
}
在这个对象上,我希望执行一些计算,但我不想修改原来的. 所以,在下面的方法中
public List<LineDescriptor> HoughTansform(IPointCloud pointCloud)
{
_dX = _settingsProvider.LfdSolverSettings.HoughSpaceStepSize.Value;
_minimumVotes = _settingsProvider.LfdSolverSettings.HoughMinimumVotesForLineSelection.Value;
if (pointCloud.Vertices.Length < 2)
throw new PointCloudException("Point cloud has less than two points");
// Add more checks once we expose for parameterization.
if (_dX < 0)
throw new ArgumentException("dX cannot be negative");
if (_minimumVotes < 2)
_minimumVotes = 2;
// Ensure we get a shallow copy.
var basePointCloud = new PointCloud(pointCloud.Vertices.Select(v => (Vertex)v.Clone()).ToArray());
var (minimumBound, maximumBound) = basePointCloud.GetBoundingBoxAsPoints();
double d = (maximumBound - minimumBound).Norm();
if (d == 0.0)
throw new PointCloudException("All points in point cloud are identical");
// ... More stuff here
我正在尝试使用数组创建一个新副本,Vertex
这样我就不会修改原始点云对象pointCloud
。我首先尝试使用简单的
var basePointCloud = new PointCloud(pointCloud.Vertices);
但这仍然出于某种原因修改了原件。按照下面的建议,我更新了这个,使用下面的“浅拷贝方法”
var basePointCloud = new PointCloud(pointCloud.Vertices.Select(v => (Vertex)v.Clone()).ToArray());
这在附加调试器时工作正常,但令人惊讶的是它在发布模式下运行时不起作用,边界框被计算为 d = 0.0 - 这很讨厌。
如何确保制作正确的干净副本,以便我的更改basePointCloud
不会影响原始pointCloud
对象?
请注意,由于使用了Vector3
结构,我无法使用任何形式的序列化创建深层副本,我的IPointCloud
对象不可序列化。
更新
它实际上是
var (minimumBound, maximumBound) = basePointCloud.GetBoundingBoxAsPoints();
这一行,为什么要返回一个元组(0.0f, 0.0f)
?我用了一些Console.WriteLine
s来看看发生了什么
- [输入
basePointCloud.GetBoundingBoxAsPoints
] 顶点长度 = 206 - [输入
basePointCloud.GetBoundingBoxAsPoints
] 最小值 = <-147.247, -109.066, 0>, 最大值 = <132.522, 167.362, 1470.4> - [
var (minimumBound, maximumBound)
= ] 最小值 = <0, 0, 0>,最大值 = <0, 0, 0> - d = 0
元组的返回和设置失败。我试过使用
(Vector3 minimumBound, Vector3 maximumBound) = basePointCloud.GetBoundingBoxAsPoints();
但这也给出了上述结果。我很困惑。
解决方案
Vertex
是引用类型。制作Vertex
数组的副本不会复制其中包含的项目。原始数组和副本都将保存对相同Vertex
对象的引用。
一种解决方案是转换Vertex
为值类型。查看类定义,如果使其不可变是一种选择,它似乎不是一个糟糕的选择。可变结构总是一个坏主意。
另一种可能的选择(糟糕!)是创建一个复制机制Vertex
,返回一个副本,然后将其投影到一个新数组:
new PointCloud(pointCloud.Vertices.Select(v => v.Copy()).ToArray());
但如果可能的话,我会推荐第一个选项。如果您需要按值复制语义,请使用类型系统提供的工具;值类型。
推荐阅读
- python - 使用参数替换将 bash 环境变量集读取到 Python 变量中
- c# - 在 asp.net core 中创建作用域工厂
- tensorflow - tf.gradients 会通过 tf.cond 吗?
- c# - 在 Unity 中创建二维箭头
- python - 如何使用 tkinter 全屏显示帧?(Python)
- c# - 防止在 Wpf RichTextBox 中删除文本
- javascript - 如何使用 Javascript 向 .NetCore 服务器创建帖子
- python - Numpy:对数组进行排序并找到相应索引的有效方法
- java - 如何仅显示文件更改事件 Java 的一条消息
- python - “是”没有定义?有点迷茫