由所有类实例共享的 C# 私有成员
本文关键字:成员 实例 共享 | 更新日期: 2023-09-27 18:32:50
我目前正在使用 Unity3D 引擎使用 C#,遇到了以下问题:
我创建了一个类,该类对它必须访问的另一个类的实例有两个私有引用。一旦我创建了类的多个实例并设置了引用,我发现所有实例都使用相同的变量。我在销毁一个实例时意识到了这一点,并且在此之前将保存引用的两个变量设置为 null。这样做之后,所有其他实例立即抛出 NullReferenceExceptions,因为它们仍在尝试访问引用。引用的对象很好,其他脚本仍然可以访问它们。
下面是一些说明结构的伪代码:
public class Character
{
// Character data
}
public class StatusEffect
{
private Character target;
private Character originator;
public void Init(Character _Target, Character _Originator)
{
target = _Target;
originator = _Originator;
}
public void Destroy()
{
target = null;
originator = null;
}
}
在程序中,它将像这样调用:
StatusEffect effect = new StatusEffect();
effect.Init(player1, player2);
// Time goes by
effect.Destroy();
调用 Destroy(( 后,每个 StatusImpact 的两个引用都将为 null。
这不仅在销毁状态效果时是一个问题,而且在创建新状态效果时也是一个问题。一旦我触摸新实例中的引用,所有状态效果都将引用新状态效果指定的两个字符。
我不明白为什么或如何解决这个问题。有人可以在这件事上启发我吗?
干杯瓦尔塔罗斯
编辑:
以下是要求的真实代码:我有一个容器类,其中包含多个状态效果。一旦启动,它就会初始化所有这些。
public class CElementTag
{
// ..Other data..
public float f_Duration; // Set in the editor
private CGladiator gl_target;
private CGladiator gl_originator;
private float f_currentDuration;
public CStatusEffect[] ar_statusEffects;
// Starts the effect of the element tag
public void StartEffect(CGladiator _Originator, CGladiator _Target)
{
gl_originator = _Originator;
gl_target = _Target;
f_currentDuration = f_Duration;
for(int i = 0; i < ar_statusEffects.Length; i++)
ar_statusEffects[i].Initialize(gl_originator, gl_target);
}
// Ends the effect of the element tag
public void EndEffect()
{
for(int i = 0; i < ar_statusEffects.Length; i++)
{
if(ar_statusEffects[i] != null)
ar_statusEffects[i].Destroy();
}
}
// Called every update, returns true if the tag can be destroyed
public bool ActivateEffect()
{
f_currentDuration -= Time.deltaTime;
if(f_currentDuration <= 0.0f)
{
EndEffect();
return true;
}
for(int i = 0; i < ar_statusEffects.Length; i++)
{
if(ar_statusEffects[i] != null && ar_statusEffects[i].Update())
RemoveStatusEffect(i);
}
return false;
}
// Removes expired status effects
private void RemoveStatusEffect(int _Index)
{
// Call destroy method
ar_statusEffects[_Index].Destroy();
// Remove effect from array
for(int i = _Index; i < ar_statusEffects.Length - 1; i++)
ar_statusEffects[i] = ar_statusEffects[i+1];
ar_statusEffects[ar_statusEffects.Length - 1] = null;
}
}
实际的 StatusEffect 类保存了两个引用以及它需要工作的其他一些数据。它具有虚拟方法,因为有一些类继承自它。
public class CStatusEffect
{
// ..Necessary data..
// References
protected CGladiator gl_target;
protected CGladiator gl_originator;
virtual public void Initialize(CGladiator _Target, CGladiator _Originator)
{
gl_target = _Target;
gl_originator = _Originator;
// ..Initialize other necessary stuff..
}
virtual public void Destroy()
{
gl_target = null;
gl_originator = null;
// ..Tidy up other data..
}
virtual public bool Update()
{
// ..Modifying data of gl_target and gl_originator..
// Returns true as soon as the effect is supposed to end.
}
}
这应该是有关此问题的所有相关代码。
编辑2
@KeithPayne 我在编辑器中定义了一个静态的元素标签数组并保存到 xml 中。在程序开始时,静态数组加载 xml 并存储所有元素标记。在创建一个要使用的新元素标签时,我利用了这个构造函数:
// Receives a static tag as parameter
public CElementTag(CElementTag _Tag)
{
i_ID = _Tag.i_ID;
str_Name = _Tag.str_Name;
enum_Type = _Tag.enum_Type;
f_Duration = _Tag.f_Duration;
ar_statusEffects = new CStatusEffect[_Tag.ar_statusEffects.Length];
Array.Copy(_Tag.ar_statusEffects, ar_statusEffects, _Tag.ar_statusEffects.Length);
}
我是否必须使用其他方法将数组复制到新标记?我认为 Array.Copy 会制作源数组的深层副本并将其存储在目标数组中。如果它实际上是在制作一个浅层副本,我明白问题现在出在哪里。
来自 Array.Copy Method (array, Array, Int32(:
如果 sourceArray 和 destinationArray 都是引用类型数组,或者 都是 Object 类型的数组,执行浅表复制。浅浅的 数组的副本是包含对相同数组的引用的新数组 元素作为原始数组。元素本身或任何东西 不会复制元素引用的内容。相比之下,深拷贝 数组直接或间接复制元素和所有内容 由元素引用。
请考虑下面这个流畅版本的StatusEffect
类及其用法:
public class StatusEffect
{
public Character Target { get; private set; }
public Character Originator { get; private set; }
public StatusEffect Init(Character target, Character originator)
{
Target = target.Clone()
Originator = originator.Clone();
return this;
}
//...
}
public CElementTag(CElementTag _Tag)
{
i_ID = _Tag.i_ID;
str_Name = _Tag.str_Name;
enum_Type = _Tag.enum_Type;
f_Duration = _Tag.f_Duration;
ar_statusEffects = _Tag.ar_statusEffects.Select(eff =>
new StatusEffect().Init(eff.Target, eff.Originator)).ToArray();
// ar_statusEffects = new CStatusEffect[_Tag.ar_statusEffects.Length];
// Array.Copy(_Tag.ar_statusEffects, ar_statusEffects, _Tag.ar_statusEffects.Length);
}
因为您是通过 Init()
方法传入对对象的引用,所以您实际上并没有"复制"这些对象,而只是在内存中维护对相同基础对象的引用。
如果您有多个players
对相同基础对象的相同引用,则玩家 1 所做的更改将影响玩家 2 正在使用的对象。
说了这么多,你实际上并没有在你的Destory
方法中释放对象。只需将本地实例引用设置为 Null,这不会影响 StatusEffects 的任何其他实例。您确定其他东西没有释放对象,或者您没有正确初始化其他实例。
如果您确实想要获取传入对象的完整副本,请查看 ICloneable 接口。看起来您想将对象的副本传递到每个播放器中。
public class Character : ICloneable
{
// Character data
//Implement Clone Method
}
public class StatusEffect
{
private Character target;
private Character originator;
public void Init(Character _Target, Character _Originator)
{
target = _Target.Clone()
originator = _Originator.Clone();
}
这些字段不会在其他实例之间共享(静态(。因此,在Destroy()
中调用target = null;
不会影响其他实例。
StatusEffect effect1 = new StatusEffect();
effect1.Init(player1, player2);
StatusEffect effect2 = new StatusEffect();
effect2.Init(player1, player2);
// Time goes by
effect2.Destroy();
// Some more time goes by
// accessing effect1.target won't give a `NullReferenceException` here unless player1 was null before passed to the init.
effect1.Destroy();
我想你确实忘记了其他实例的Init(..)
。每次创建 StatusEffect
实例时,都需要调用 Init(...)
。
更新:
此行将清除对效果的引用,但您永远不会重新创建它:
ar_statusEffects[ar_statusEffects.Length - 1] = null;
所以下次调用ar_statusEffects[x].Update() or Initialize() etc
时,它将抛出一个 NullReferenceException
如果你想在数组中clear out
效果,你可以在效果中创建一个Enable
布尔值,这样你只需要设置/重置它。
for(int i = 0; i < ar_statusEffects.Length; i++)
if(ar_statusEffects[i].IsEnabled)
ar_statusEffects[i].Update();
为什么不使用列表?只要您不必在其中洗牌,数组就会更快。(如循环缓冲器等(
了基思·佩恩,我才知道问题出在哪里。我正在创建 CElementTag 的深层副本,但不是我的ar_statusEffects数组。我错误地认为 Array.Copy 正在创建数组的深层副本,而实际上并非如此。
我为我的 CStatusImpact 实现了 IClonable 接口,并使用 Clone(( 方法为静态数组的每个成员创建一个真正的深度副本,并将其添加到数组ar_statusEffects新标签中。这样,我就有了单独的效果实例,而不是对同一静态效果的引用。
感谢大家,尤其是基思佩恩,感谢他们的帮助和支持!