I am currently working with C# using the Unity3D engine and have come upon the following problem:
I created a class that has two private references to instances of another class which it has to access. Once I create multiple instances of the class and set the references I found out that all instances were using the same variable. I realized this as I was destroying an instance and just before that set the two variables holding the references to null. Immediately after doing that all other instances were throwing NullReferenceExceptions because they were still trying to access the references. The referenced objects are fine, other scripts can still access them.
Here is some pseudo code illustrating the structure:
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;
}
}
In the program it would be called like this:
StatusEffect effect = new StatusEffect();
effect.Init(player1, player2);
// Time goes by
effect.Destroy();
After calling Destroy() every StatusEffect's two references will be null.
This is not only an issue when destroying StatusEffects, but also when creating new ones. As soon as I touch the references from within a new instance all StatusEffects will reference the two Characters specified by the new StatusEffect.
I do not understand why or how I can fix this issue. Can someone enlighten me on this matter?
Cheers,
Valtaroth
EDIT:
Here is the real code as requested:
I have a container class holding several StatusEffects. As soon as it starts, it initializes all of them.
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;
}
}
The actual StatusEffect class is holding the two references as well as some other data it needs to work. It has virtual methods because there are some classes inheriting from it.
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.
}
}
That should be all the relevant code concerning this problem.
EDIT2
#KeithPayne I have a static array of ElementTags defined in the editor and saved to xml. At the beginning of the program the static array is loading the xml and stores all element tags. When creating a new element tag to use I utilize this constructor:
// 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);
}
Do I have to use a different method to copy the array to the new tag? I thought Array.Copy would make a deep copy of the source array and stored it in the destination array. If it is in fact making a shallow copy, I understand where the problem is coming from now.
From Array.Copy Method (Array, Array, Int32):
If sourceArray and destinationArray are both reference-type arrays or
are both arrays of type Object, a shallow copy is performed. A shallow
copy of an Array is a new Array containing references to the same
elements as the original Array. The elements themselves or anything
referenced by the elements are not copied. In contrast, a deep copy of
an Array copies the elements and everything directly or indirectly
referenced by the elements.
Consider this fluent version of the StatusEffect class and its usage below:
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);
}
Because you're passing in references to the objects via your Init() method, you're not actually "copying" the objects, just maintaining a reference to the same underlying objects in memory.
If you have multiple players with the same references to the same underlying objects, then changes made by player 1 will effect the objects being used by player 2.
Having said all that, you're not actually disposing the objects in your Destory method. Just setting the local instance references to Null which shouldn't affect any other instances of StatusEffects. Are you sure something else isn't disposing the objects, or that you haven't properly init'd your other instances.
If you do want to take a full copy of the passed in objects, take a look at the ICloneable interface. It looks like you want to pass in a copy of the objects into each Player.
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();
}
The fields aren't shared(static) among other instances. So calling target = null; in Destroy() won't affect other instances.
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();
I think you did forget the Init(..) on the other instances. Every time you create an instance of StatusEffect, you need to call Init(...).
Update:
This line will clear the reference to the effect, but you never recreate it:
ar_statusEffects[ar_statusEffects.Length - 1] = null;
so the next time you call ar_statusEffects[x].Update() or Initialize() etc it will throw a NullReferenceException
If you want to clear out effects within you array, you could create an Enable bool in the effect, this way you only have to set/reset it.
for(int i = 0; i < ar_statusEffects.Length; i++)
if(ar_statusEffects[i].IsEnabled)
ar_statusEffects[i].Update();
Why don't you use a List instead? Arrays will be faster as long you don't have to shuffle in it. (like circulair buffers etc)
Thanks to Keith Payne I figured out where the problem was. I was creating a deep copy of CElementTag, but not of my ar_statusEffects array. I wrongly assumed Array.Copy was creating a deep copy of an array when it actually was not.
I implemented the IClonable interface for my CStatusEffect and use the Clone() method to create a true deep copy for each member of the static array and add it to the new tags ar_statusEffects array. This way I have seperate instances of the effects instead of references to the same static effect.
Thanks to everyone, especially Keith Payne, for their help and support!
Related
I have this problem in Unity (or maybe C#) that's very weird to me. Here is a virtual class:
public abstract class ActionTaken : MonoBehaviour {
protected char type;
protected Transform minionTakingAction;
public abstract void activate();
}
And this virtual class is a parent to the one that interests me:
public class AbilityTaken : ActionTaken
{
public int index;
List<Transform> selectedFriendlyMinions;
List<Transform> selectedEnemyMinions;
List<Transform> selectedTiles;
public override void activate()
{
//The value here is 0 !!! And it should be 1...
Debug.Log(selectedEnemyMinions.Count);
if (selectedFriendlyMinions.Count == 0 && selectedEnemyMinions.Count == 0 && selectedTiles.Count == 0 )
{
minionTakingAction.GetComponentInParent<AbilitiesActivation>().activateAbility(index);
}
else
{
minionTakingAction.GetComponentInParent<AbilitiesActivation>().activateAbility(index, selectedFriendlyMinions, selectedEnemyMinions, selectedTiles);
}
}
public AbilityTaken(Transform _minionTakingAction, int abilityIndex, List<Transform> _selectedFriendlyMinions, List<Transform> _selectedEnemyMinions, List<Transform> _selectedTiles)
{
type = 'S';
minionTakingAction = _minionTakingAction;
index = abilityIndex;
selectedEnemyMinions = _selectedEnemyMinions;
selectedFriendlyMinions = _selectedFriendlyMinions;
selectedTiles = _selectedTiles;
//The value here is 1 !!!
Debug.Log(selectedEnemyMinions.Count);
}
public AbilityTaken(Transform _minionTakingAction, int abilityIndex)
{
type = 'S';
minionTakingAction = _minionTakingAction;
index = abilityIndex;
selectedFriendlyMinions = new List<Transform>();
selectedEnemyMinions = new List<Transform>();
selectedTiles = new List<Transform>();
}
}
As you can see in the comments, the value of selectedEnemyMinions list of Transforms changes from the constructor (count value: 1) to the "activate()" function (count value: 0), without me making any changes to it. All I do is:
1. I create a new instance of AbilityTaken giving to the constructor an enemyMinionSelection list with 1 element
2. I add abilityTaken to a list
3. I call activate() from LateUpdate()
AbilityTaken abilityTaken = new AbilityTaken(minionTakingAction, gameMaster.abilitySelected,
// value of enemyMinionSelected.Count here is 1
new List<Transform>(), gameMaster.enemyMinionsSelected, new List<Transform>());
List<ActionsTaken> actionsTaken = new List<ActionTaken>();
actionsTaken.Add(abilityTaken);
private void LateUpdate()
{
if (!actionInProgress &&
actionsTaken.Count>0)
{
ActionTaken currentAction = actionsTaken[0];
currentAction.activate();
}
}
If you can tell me why adding the class instance to List and accessing this instance would cause a member List of Transforms to change their value, that would be great. On the other hand the value of member variable "index" doesn't change (it's always 1). I tried changing Lists to public but that doesn't help as expected.
If you can tell me why adding the class instance to List and accessing this instance would cause a member List of Transforms to change their value, that would be great.
It's not. There's nothing wrong with doing that, it won't change the current values of AbilityTaken.
There is something else going on, that is, the problem is somewhere else. For example, Debug.Log(selectedEnemyMinions); is not printing the Count.
I thoroughly debugged my code and found that the problem here is that I am giving the
constructor of ActionTaken parameters (Namely List) that get changed after assignment in ActionTaken constructor. Then I execute some code that changes the first list of transforms, but due to C# not copying the value, but referencing the assigned value the List of Transforms changes when the firstly assigned value changes.
void function()
{
//Some code here
AbilityTaken abilityTaken = new AbilityTaken(minionTakingAction, gameMaster.abilitySelected, new List<Transform>(), gameMaster.enemyMinionsSelected, new List<Transform>())
//Here we go in the constructor and everything seems fine
// ! Some code here that changes gameMaster.enemyMinionsSelected. The class object doesn't copy the List of Transforms, but references it's address and when it changes, the reference in ActionTaken also changes.
}
I'm learning c# and have decided to try and create a functioning chance game, part by part.
I previously created a method that would create a random (yet likely inefficient) array of natural numbers that would not appear more than once.
However, as I try to piece together OOP I realised if I create multiple of these arrays they would be objects, thus should be created by a class.
I have the array created inside a constructor. Yet I cannot access this array from either within the constructor's class or in another class entirely.
class randomArray
{
Random rng = new Random();
protected int amountOfNumbers;
protected int rangeOfNumbers;
public randomArray(int amountOfNumbers, int rangeOfNumbers)
{
this.amountOfNumbers = amountOfNumbers;
this.rangeOfNumbers = rangeOfNumbers;
int[] randomizedArray = new int[amountOfNumbers];
for (int i = 0; i < amountOfNumbers; i++)
{
randomizedArray[i] = rng.Next(1, rangeOfNumbers + 1);
// A test to ensure that each new number generate is not
already part of the array.
for (int j = 0; j < i; j++)
{
while (randomizedArray[i] == randomizedArray[j])
{
randomizedArray[i] = rng.Next(1, rangeOfNumbers + 1);
j = 0;
}
if (randomizedArray[i] != randomizedArray[j])
continue;
}
}
}
public int RangeOfNumbers { get; set; }
public int AmountOfNumbers { get; set; }
I believe I'm failing to either understand the fundamentals of OOP or I am I failing to understand how to utilize classes.
Make your array a member of the actual class, ie property
public class randomArray
{
public int[] RandomizedArray { get; set; }
...
At about this time, you should probably have a read through this
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/class
https://learn.microsoft.com/en-us/dotnet/csharp/properties
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/fields
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constructors
Update
public randomArray(int amountOfNumbers, int rangeOfNumbers)
{
...
RandomizedArray = new int[amountOfNumbers]
// do stuff here
Although the response of #TheGeneral contains the solution and points you towards more documentation for learning about OOP.
I think it is better to give an explanation why your code did not work.
Everything in OOP has a Scope (a certain "area" where it is available) which is, for most of the things in OOP, fenced of by the brackets.
In this instance the scope is based around the constructor, which causes the variables declared in the brackets to only be available inside the brackets. Except when you use an "outside" link like a class variable or property.
public class Example
{
// this is a class variable, this variable is now reachable from outside the class
// definition.
public int aClassVariable;
// this is a class property which because we added the get and set calls generate
// automatically an get and set method (internally)
public bool aClassProperty { get; set; }
public Example()
{
// to set the class variable and property you just give them a value.
aClassVariable = 42;
aClassProperty = true;
// this variable is not available outside the scope of this function,
// this is because you declared the variable inside this function.
// So the variable is only available inside this function as long as this
// function runs (or as it is called "is in scope").
int[] arr = new int[10];
}
}
Also pay attention about the differences between variables and properties, a variable is something every OOP language contains.
But the properties are actually an extension for the variables where the accessing and setting can be modified with a definition of the get and set method.
I would strongly suggest to read the documentation linked from the answer of TheGeneral because it contains far more information about the intricacies of OOP and C# itself.
I wanted to understand more about how the ref keyword works so I made the following experiment:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
var main = new Main { Property = 1 };
var dependent = new Dependent(main);
void ChangeRef(ref Main Oldmain, Main newMain)
{
Oldmain = newMain;
}
ChangeRef(ref main, new Main { Property = 5 });
Assert.AreEqual(5,dependent.Main.Property);
}
}
public class Main
{
public int Property { get; set; }
}
public class Dependent
{
public Dependent(Main main)
{
Main = main;
}
public Main Main { get; set; }
}
As you can see I was expecting to be able to replace the object that main was referencing while keeping the reference, but the test fails and with the value still being 1. Could someone elaborate abit on why that wasnt working or point me to a place where I can read more?
Update:
Like someone answered below, but later removed.
Why doesnt it work if I pass the main object by reference to the dependent in the constructor?
Shouldnt they all have the same reference?
As others have pointed, you cannot instantly make all variables and fields in your program point to a different instance.
But if you want to reflect a change in all parts of the program, the simplest way is to wrap it in a different class (like your Dependent class). Then you can share the class with other parts of the program, and change its properties instead:
class SomeOtherObject
{
readonly Dependent _dependent;
public Dependent { get { return _dependent; }}
public SomeOtherObject(Dependent dependent)
{
_dependent = dependent;
}
public void Print()
{
Console.WriteLine(_dependent.Main.Property);
}
}
So now you can do this:
var dependent = new Dependent(new Main { Property = 1 });
var someOtherObject = new SomeOtherObject(dependent);
// this will print "1"
someOtherObject.Print();
dependent.Main = new Main { Property = 5; };
// this will print "5"
someOtherObject.Print();
In this case, obviously, simply changing dependent.Main.Property would also do the trick. So, if all parts of your program point to a single object, you can mutate it (i.e. change its internal data) and everyone will see the change, but you cannot make all parts of your program change what they are pointing to.
It's worth noting that you need to be careful when doing this in multithreaded programs; you rarely want some other thread to be able to randomly change your internal data.
That's also why it's best to try to keep your properties readonly, and your objects immutable, if possible.
After doing that
var main = new Main { Property = 1 };
You have object of type Main allocated somewhere in memory (let's name it Main1), at some memory address X, and variable main points to that object. "Points" means it literally stores address of that object Main1, so main contains X.
Then you pass reference to Main1 to the constructor of Dependent object
var dependent = new Dependent(main);
Dependent object is also allocated somewhere in memory, and one of its fields stores reference to Main1 object. So dependent.Main also stores X.
When you do
ChangeRef(ref main, new Main { Property = 5 });
You allocate new object Main5 somewhere at memory address Y. Now you change what address variable main points to. Before it stored address X (address of Main1), now it stores address Y (address of Main5). But dependent.Main still stores address X, because you didn't change it in any way, so it still points to object Main1.
I have one class that is going to contain multiple lists that hold a variety of game objects. These lists will get populated by JSON scripts further in development.
Right now, I'm trying to access 1 element in 1 list. I have filled my class out as so:
public class ShopManager : MonoBehaviour
{
public List<GameObject> primaryWeapons = new List<GameObject>();
public List<GameObject> PrimaryWeapons
{
get { return primaryWeapons; }
}
public GameObject gameObj1, gameObj2;
// Use this for initialization
void Start ()
{
FillList();
}
public void FillList()
{
primaryWeapons.Add(gameObj1);
primaryWeapons.Add(gameObj2);
}
}
In my second class I'm trying to access one of the game objects I have placed in the list. This what I have so far:
ShopManager primary;
public GameObject temp;
public List<GameObject> tmpList;
void Awake()
{
primary = new ShopManager();
primary.FillList();
tmpList= new List<GameObject>();
}
// Use this for initialization
void Start()
{
for (int i = 0; i < primary.PrimaryWeapons.Count; i++)
{
tmpList.Add(primary.PrimaryWeapons[i]);
}
Debug.Log(primary.PrimaryWeapons.Count);
}
public void SelectWeapon1()
{
temp = primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault();
}
}
In my list in the shop manager class I am manually setting the objects myself, so I know the names of them. However, whilst I can get a count returning correctly, I am unable to access this named object.
When I run the code I get a null reference pointing to the following line:
temp = primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault();
Additonally I even created another list with the idea of passing the contents from my List property in the ShopManager class to this temp one. However, this lists populates with 2 empty positions.
I'm still not 100% on using properties like this. Espcially with lists. Could someone please tell me what it is I'm doing wrong?
You didn't initialize your objects in ShopManager.
Replace this:
public GameObject gameObj1, gameObj2;
With this:
public GameObject gameObj1 = new GameObject(), gameObj2 = new GameObject();
Check GameObject initialization. Especially gameObject field.
primary.PrimaryWeapons.Where(obj => obj.gameObject.name == "DoorParts_1").SingleOrDefault(); can fail with null reference in just few conditions:
primary is null
primary.PrimaryWeapons is null
obj.gameObject is null
The first two look covered in your code. So, check how GameObject.gameObject field is initialized.
Consider following code, I am trying to implement custom link list.
Expected Output : Start-->123-->11-->NULL
Actual Output : Start-->11-->NULL
class MyLinkList
{
public object data { get; set; }
public MyLinkList Next { get; set; }
}
public static void PrintLinkList(MyLinkList start)
{
Console.Write("Start-->");
while (start != null)
{
Console.Write(start.data + "-->");
start = start.Next;
}
Console.Write("NULL");
}
public static void AddNodeStart( MyLinkList start, object data)
{
MyLinkList newNode = new MyLinkList();
newNode.data = data;
newNode.Next = start;
start = newNode;
}
public static void Main()
{
MyLinkList n = new MyLinkList() { data = 11, Next = null };
AddNodeStart( n,123);
PrintLinkList(n);
Console.ReadLine();
}
The problem is even though node is added to the list in the AddToStart function, its value is not persisted when control come back to main function.
Object is passed by reference then why its value is not getting persisted. if I use 'ref' word then I get the expected result.
Atul sureka
Add ref to your method declaration like so:
public static void AddNodeStart(ref MyLinkList start, object data)
then call it
AddNodeStart(ref n,123);
and it should work.
If you don't, then start is just a variable inside AddNodeStart method - assigning values to it will not change reference stored in n.
That said it is rather a bad taste to do this. Instead consider returning the new node from your add method.
public static MyLinkList AddNodeStart(MyLinkList start, object data)
{
MyLinkList newNode = new MyLinkList();
newNode.data = data;
newNode.Next = start;
return newNode;
}
then call it this way:
n = AddNodeStart(n,123);
Because the reference in C# (also in Java) is passed by value. The actually reference used in the method is the copy of the reference value passed in. (The object being referred is not copied)
Another example is you can't swap 2 objects by passing the references into a method. Since all references are passed by value, what you actually swap is the values of the copied references.
Go the answer from Why use the 'ref' keyword when passing an object?.
Value is not getting persisted because I am creating a new object in AddNodeStart. If I modify the existing object in the function then the changes would be persisted.