Related
I'm looking for a way to replace some of my List objects with arrays in my project to boost performance. The reasoning is that the List I would replace do not change size often (sometimes only once), so it would make sense to swap them out with arrays. Also, I would like to prevent allocations on the heap as much as possible.
So my idea is to create a struct (ArrayStruct) with two members "Count" and "Array". The struct would have some functions to add/remove/get/set/ect... elements in the array. The count would keep count of the elements that are usable in the array, and the array would only increase in size, never decrease.
Note that this is for a very particular case because I know the array size won't be very big.
The main problem I have is when I pass the reference to an already existing ArrayStruct object (named a in the example) to another (named b in the example) - I can edit the array in it, but once I resize it in a, I get an out of range exception in b. Is this because of the way I'm updating the reference via System.Array.Resize, so that the object b somehow does not attribute this change?
The only thing I could come up with as an idea was trying to override the assignment operator so that it would create a new array on assignment... but obviously that does not work.
My ArrayStruct
public struct ArrayStruct<T>
{
[SerializeField] private int m_Count;
[SerializeField] private T[] m_Array;
public int Count => m_Count;
public void Initialize(int startSize)
{
m_Count = 0;
m_Array = new T[startSize];
}
public T GetAt(int index)
{
return m_Array[index];
}
public void SetAt(int index, T newValue)
{
m_Array[index] = newValue;
}
public void Add(T newElement)
{
if (m_Array == null) {
m_Array = new T[1];
} else if (m_Array.Length == m_Count) {
System.Array.Resize(ref m_Array, m_Count + 1);
}
m_Array[m_Count] = newElement;
m_Count++;
}
public void Remove(T element)
{
for (int i = 0; i < m_Count; ++i) {
if (!element.Equals(m_Array[i])) {
continue;
}
for (int j = index; j < m_Count - 1; ++j) {
m_Array[j] = m_Array[j + 1];
}
m_Count--;
m_Array[m_Count] = default(T);
break;
}
}
//Trying to overload the = operating by creating a auto cast, this gives a compile error
/*public static implicit operator ArrayStruct<T>(ArrayStruct<T> otherArray)
{
var newArray = new ArrayStruct<T>();
newArray.m_Count = otherArray.Count;
newArray.m_Array = new T[otherArray.Length];
otherArray.m_Array.CopyTo(newArray.m_Array);
return newArray;
}*/
An example showcasing the problem
var a = new ArrayStruct<string>()
a.Add("hello");
var b = a;
print (b.GetAt(0)); //"hello"
a.SetAt(0, "new value for a");
print(b.GetAt(0));//"new value for a" , changing a changed b because the array is the same in both, this is bad
a.Add("resizing array");
print (b.GetAt(1)); //"Out of range exception" , because the array was resized for a but not for b therefore the connection broke
So do any of you have an idea of how I could make a new copy the array when I assign the struct to another variable?
Of course, I know I could use a function to do something like so
b = new ArrayStruct(a);
But I want it to be implicit. Or if there was a way to prevent assignments that would also work for me.
var a = new ArrayStruct();//let this work
var b = a; //Prevent this from happening
If you have any other solutions for replacing Lists with arrays conveniently please let me know
So do any of you have an idea of how I could make a new copy the array when I assign the struct to another variable? Of course, I know I could use a function to do something like so b = new ArrayStruct(a); But I want it to be implicit.
You can't do that. If a is a struct and you execute var b = a, then it will always just set b to a shallow copy of a; there's no way to change that behavior.
Doing something like b = new ArrayStruct(a) is the best you can do.
You're not understanding what the "=" operator is doing there. Objects are defined by reference, meaning when you write var b = a; you are actually asking for what you wish to avoid to happen, that is - object "b", pointing to the same object, which is "a".
You're passing the object "b" to point to the reference of object "a", which object holds the address to the object in question you defined, using the new keyword var a = new ArrayStruct<string>()
Your operator overloading does not work because you can't use it for conversions of the same type as the class you're writing the operator in. You'd either have to make a separate object (class), which would defeat the point you're trying to fix, or pass the m_array instead: public static implicit operator ArrayStruct<T>(T[] array)
You can also do something like this:
public static ArrayStruct<T> CopyFrom (ArrayStruct<T> array)
{
var newArray = new ArrayStruct<T>();
newArray.m_Array = new T[array.Count + 1];
newArray.Count = array.Count;
Array.Copy(array.m_Array, 0, newArray.m_Array, 0, array.m_Array.Length);
return newArray;
}
Now it should work exactly as you wanted.
var a = new ArrayStruct<string>();
a.Add("hello");
var b = ArrayStruct<string>.CopyFrom(a);
a.SetAt(0, "new value for a");
a.Add("resizing array");
Console.WriteLine(b.Count); // b returns 1
Console.WriteLine(a.Count); // a returns 2
As to why your index was going out of bounds, i.e it did not preserve the reference found in "a", this is all in correlation with references, yet again.
In the following code System.Array.Resize(ref m_Array, Count + 1); you're changing the reference found for object m_array in "a", when you're adding the new element with the line a.Add("resizing array");. That essentially means that your "a" object's m_array (which is another object of type array[]) is now pointing to a new object (of type array[]), which is of a new size.
Okay, but object "a" is now using the new reference, whilst object "b" is still using the old one. m_array in object "b" only has a single element, while the new m_array object in "a" has the newly added element "resizing array", along with any previously added ones.
I believe that's how System.Array.Resize works? Edit me if I'm wrong.
Below is a resource explaining how to do a deep cloning of an object, if that's what you truly wish to do (i.e create a completely new copy of an object, as well as any other objects inside)
Deep cloning of objects
Could someone please explain the behavior of this
class testCompile
{
/*
* Sample Code For Purpose of Illustration
*/
struct person
{
public int age;
public string name;
}
static void Main(string[] args)
{
List<person> Listperson = new List<person>();
person myperson = new person();
for (int i = 1; i <= 2; i++)
{
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
}
int x = 0;
while (x < Listperson.Count)
{
//Output values
Console.WriteLine("{0} - {1}", Listperson[x].name, Listperson[x].age);
x++;
}
}
}
/*
Output:
Person - 1 - 23
Person - 2 - 24
*/
Why am I not getting the same output for a class as that of a struct?
class testCompile
{
/*
* Sample Code For Purpose of Illustration
*/
class person
{
public int age;
public string name;
}
static void Main(string[] args)
{
List<person> Listperson = new List<person>();
person myperson = new person();
for (int i = 1; i <= 2; i++)
{
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
}
int x = 0;
while (x < Listperson.Count)
{
//Output values
Console.WriteLine("{0} - {1}", Listperson[x].name, Listperson[x].age);
x++;
}
}
}
/*
Output:
Person - 2 - 24
Person - 2 - 24
*/
Classes are reference types, structs are value types.
When a value type is passed to a method as a parameter, a copy of it will be passed through. That means that you add two completely separate copies of the Person struct, one for each pass in the loop.
When a reference type is passed to a method as a parameter, the reference will be passed through. That mean that you add two copies of the reference to the same memory location (to the same Person object) - when making changes to this one object, you see it reflected in both references since they both reference the same object.
It's the difference between value type (struct) and reference type (class).
When you're adding the struct to Listperson the content of person is put in the list, you have two different person struct in your list.
for (int i = 1; i <= 2; i++)
{
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
/* First time:
Listperson contains a person struct with value { age = 23, name = 1}
Second iteration:
Listperson contains a person struct with value { age = 23, name = 1}
Listperson contains another person struct with value { age = 24, name = 2}
*/
}
When you're adding the class the reference is put in the list, you have two references that referenced the same person object.
for (int i = 1; i <= 2; i++)
{
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
/* First time:
Listperson contains 1 reference to myperson object with value { age = 23, name = 1}
Second iteration:
Listperson contains 2 reference to myperson object with value { age = 24, name = 2}
*/
}
Because your myperson variable only ever deals with one person struct/class.
What you add to the list, in your loop, is a copy of your myperson variable - which for the struct, will be an entire copy of the struct, but for the class will be a copy of the reference to the single instance that you create (and mutate).
If you want same result then bring person declaration inside of the for loop:-
// person myperson = new person();
//Move the upper line inside the for loop
for (int i = 1; i <= 2; i++)
{
person myperson = new person();
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
}
In struct you adding a value type hence separate values are stored, whereas in class you are adding reference to the object hence gettng same value.
In the second instance, you're addding a reference type. In fact, you're adding the same item, twice, since your
= new person()
is not in the loop. So it always points to the same object you initialized here:
person myperson = new person();
Even after it's added to your list, the changes affect it.
In the first instance, you're adding a struct each time, which is a value type, so will be copied into the list. Changes you make after that no longer refer to the object in the list, so they have different values.
Structures are value types and classes are reference types. So in your first example when you add myperson to the list your adding a copy of myperson and the myperson variable still refers to a separate copy. In you second example myperson is a reference type so your adding two pointers to the same object.
You should understand key distinctions between structs (Value Types) and classes (Reference Type). You could easily find this information in Google or at SO.
When you add struct instance to List you create another separate copy for this instance, and when you change one element you did not change another.
But in case of classes you create one instance and uses this one "shared" instance with two references (list[0] and list1) and you could change this one instance through two different references, that's why when you change list[0] item it seems that you change list1 item too.
Consider following code:
var s1 = new SampleStruct { X = 1, Y = 1 };
var s2 = s1;
//Creating separate copy
//Lets check this
Console.WriteLine(object.ReferenceEquals(s1, s2)); //Prints False
var c1 = new SampleClass { X = 1, Y = 2 };
var c2 = c1;
//We do not create any copy
// two references c1 and c2 "pointed" to one shared object
Console.WriteLine(object.ReferenceEquals(c1, c2)); //Prints True
Similar behavior we have when we pass parameter to function (or adding element to list).
In the second example you're only creating on item and adding a reference to to the list many times.
When you add the struct to the collection, it makes a copy of it. It's a value type. You'll end up with two distinct objects in the collection, each with different values. This is probably the expected behavior.
When you add the class, the reference type, to the collection, a new object is not created. You're actually adding two different references to the same object. You'll end up with (apparently) two objects with the same value. It's actually the same object, seemingly appearing twice in the collection.
Think of variables and parameters of class types as holding an "instance IDs". The only things one can actually do directly with an instance ID are (1) create a new one (which will be assigned to a new instance of a class), (2) assign one to another, or (3) check two IDs to see if they are equal. Doing anything else with a variable, parameter, etc. of a class type is a short hand for "do _ to the instance referred to this instance ID".
So code like:
{
Car A,B,C; /* Car is a class */
A = new Car;
B = new Car;
C = A;
A.color = carColors.Yellow;
B.color = C.color;
}
The first "new" statement will create an instance of Car and put its instance ID (let's say #1234) in "A". The second will create another car instance (#4321) and store its ID in B. The next statement will copy #1234 into C. It doesn't do anything with the car--it just copies the ID. Then car #1234 will be painted yellow, then in the last statement, the color of car #1234 (i.e. yellow) will be used to paint car #4321. Note that while A and C are different variables, they both hold the same instance ID (#1234) and thus refer to the same car.
Note: This applies to both List and ArrayList
Take a look at the following simple code:
class Creature
{
public string Name;
}
class Game
{
// This is a reference type
public Creature CurrentCreature;
}
class Program
{
static void Main(string[] args)
{
// First, we'll create 2 objects and let the
// reference type "CurrentCreature" points to one of them
Creature dragon = new Creature();
dragon.Name = "Dragon";
Creature Unicorn = new Creature();
dragon.Name = "Unicorn";
Game game = new Game();
game.CurrentCreature = dragon;
// Now we'll create a list which will contain
// the reference type "CurrentCreature"
List<Creature> list = new List<Creature>();
list.Add(game.CurrentCreature);
foreach (Creature c in list)
{
Console.WriteLine(c.Name); // Output is "Dragon"
}
// Now, we'll let "CurrentCreature" point to a different object
game.CurrentCreature = unicorn;
// The reference in the list still pointing to the original object!!!
foreach (Creature c in list)
{
Console.WriteLine(c.Name); // Output is "Dragon"!!!
}
Console.ReadLine();
}
}
I checked how a list adds an item and there is no instantiation of a new object. This is List.Add method (using Reflector tool)
public void Add(T item)
{
if (this._size == this._items.Length)
{
this.EnsureCapacity(this._size + 1);
}
this._items[this._size++] = item; // No instantiation
this._version++;
}
So, why is this happenning? The element in the list should be a reference to the object pointed to by "CurrentCreature" or is it not? Isn't it similar to the following code if we remove the list?:
class A
{
public B X;
}
class B
{
public string Name;
}
....
A a = new A();
B b1 = new B(); b1.Name = "b1";
B b2 = new B(); b2.Name = "b2";
a.X = b1;
Console.WriteLine(a.X.Name); // output: b1
b1 = b2;
Console.WriteLine(a.X.Name); // output: b1
When you do
game.CurrentCreature = unicorn;
You overwrite the pointer in game.CurrentCreature with one to the unicorn object. The array still has a pointer to the dragon object. It shouldn't be changed, this is how pointers work.
Edit:
A little explanation of what happens with the pointers:
First you created 2 objects
Creature dragon = new Creature();
dragon.Name = "Dragon";
Creature Unicorn = new Creature();
dragon.Name = "Unicorn";
This made dragon have a pointer to your dragon object, and unicorn have a pointer to your unicorn object.
Then you set the pointer of game.CurrentCreature to dragon's pointer.
game.CurrentCreature = dragon;
Then you add a pointer to dragon, the current creature, to the list
List<Creature> list = new List<Creature>();
list.Add(game.CurrentCreature);
Then you replace the pointer in game.CurrentCreature(was dragon) with a pointer to the unicorn object.
game.CurrentCreature = unicorn;
This will in no way affect the pointer held in the dragon object.
Cheers,
Reference types don't just change.
You're adding the creature to the list, not the game. Then you change the game's reference to use anoter creature; but the creature referenced in the list remains the same, thus it outputs the same result again.
Reference is copied to a list, not an object. After list.Add(myobject); you get two references (that are referring to the same object): myobject and those one that is in a list.
Could someone please explain the behavior of this
class testCompile
{
/*
* Sample Code For Purpose of Illustration
*/
struct person
{
public int age;
public string name;
}
static void Main(string[] args)
{
List<person> Listperson = new List<person>();
person myperson = new person();
for (int i = 1; i <= 2; i++)
{
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
}
int x = 0;
while (x < Listperson.Count)
{
//Output values
Console.WriteLine("{0} - {1}", Listperson[x].name, Listperson[x].age);
x++;
}
}
}
/*
Output:
Person - 1 - 23
Person - 2 - 24
*/
Why am I not getting the same output for a class as that of a struct?
class testCompile
{
/*
* Sample Code For Purpose of Illustration
*/
class person
{
public int age;
public string name;
}
static void Main(string[] args)
{
List<person> Listperson = new List<person>();
person myperson = new person();
for (int i = 1; i <= 2; i++)
{
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
}
int x = 0;
while (x < Listperson.Count)
{
//Output values
Console.WriteLine("{0} - {1}", Listperson[x].name, Listperson[x].age);
x++;
}
}
}
/*
Output:
Person - 2 - 24
Person - 2 - 24
*/
Classes are reference types, structs are value types.
When a value type is passed to a method as a parameter, a copy of it will be passed through. That means that you add two completely separate copies of the Person struct, one for each pass in the loop.
When a reference type is passed to a method as a parameter, the reference will be passed through. That mean that you add two copies of the reference to the same memory location (to the same Person object) - when making changes to this one object, you see it reflected in both references since they both reference the same object.
It's the difference between value type (struct) and reference type (class).
When you're adding the struct to Listperson the content of person is put in the list, you have two different person struct in your list.
for (int i = 1; i <= 2; i++)
{
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
/* First time:
Listperson contains a person struct with value { age = 23, name = 1}
Second iteration:
Listperson contains a person struct with value { age = 23, name = 1}
Listperson contains another person struct with value { age = 24, name = 2}
*/
}
When you're adding the class the reference is put in the list, you have two references that referenced the same person object.
for (int i = 1; i <= 2; i++)
{
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
/* First time:
Listperson contains 1 reference to myperson object with value { age = 23, name = 1}
Second iteration:
Listperson contains 2 reference to myperson object with value { age = 24, name = 2}
*/
}
Because your myperson variable only ever deals with one person struct/class.
What you add to the list, in your loop, is a copy of your myperson variable - which for the struct, will be an entire copy of the struct, but for the class will be a copy of the reference to the single instance that you create (and mutate).
If you want same result then bring person declaration inside of the for loop:-
// person myperson = new person();
//Move the upper line inside the for loop
for (int i = 1; i <= 2; i++)
{
person myperson = new person();
//Assignment
myperson.age = 22+i;
myperson.name = "Person - " + i.ToString();
Listperson.Add(myperson);
}
In struct you adding a value type hence separate values are stored, whereas in class you are adding reference to the object hence gettng same value.
In the second instance, you're addding a reference type. In fact, you're adding the same item, twice, since your
= new person()
is not in the loop. So it always points to the same object you initialized here:
person myperson = new person();
Even after it's added to your list, the changes affect it.
In the first instance, you're adding a struct each time, which is a value type, so will be copied into the list. Changes you make after that no longer refer to the object in the list, so they have different values.
Structures are value types and classes are reference types. So in your first example when you add myperson to the list your adding a copy of myperson and the myperson variable still refers to a separate copy. In you second example myperson is a reference type so your adding two pointers to the same object.
You should understand key distinctions between structs (Value Types) and classes (Reference Type). You could easily find this information in Google or at SO.
When you add struct instance to List you create another separate copy for this instance, and when you change one element you did not change another.
But in case of classes you create one instance and uses this one "shared" instance with two references (list[0] and list1) and you could change this one instance through two different references, that's why when you change list[0] item it seems that you change list1 item too.
Consider following code:
var s1 = new SampleStruct { X = 1, Y = 1 };
var s2 = s1;
//Creating separate copy
//Lets check this
Console.WriteLine(object.ReferenceEquals(s1, s2)); //Prints False
var c1 = new SampleClass { X = 1, Y = 2 };
var c2 = c1;
//We do not create any copy
// two references c1 and c2 "pointed" to one shared object
Console.WriteLine(object.ReferenceEquals(c1, c2)); //Prints True
Similar behavior we have when we pass parameter to function (or adding element to list).
In the second example you're only creating on item and adding a reference to to the list many times.
When you add the struct to the collection, it makes a copy of it. It's a value type. You'll end up with two distinct objects in the collection, each with different values. This is probably the expected behavior.
When you add the class, the reference type, to the collection, a new object is not created. You're actually adding two different references to the same object. You'll end up with (apparently) two objects with the same value. It's actually the same object, seemingly appearing twice in the collection.
Think of variables and parameters of class types as holding an "instance IDs". The only things one can actually do directly with an instance ID are (1) create a new one (which will be assigned to a new instance of a class), (2) assign one to another, or (3) check two IDs to see if they are equal. Doing anything else with a variable, parameter, etc. of a class type is a short hand for "do _ to the instance referred to this instance ID".
So code like:
{
Car A,B,C; /* Car is a class */
A = new Car;
B = new Car;
C = A;
A.color = carColors.Yellow;
B.color = C.color;
}
The first "new" statement will create an instance of Car and put its instance ID (let's say #1234) in "A". The second will create another car instance (#4321) and store its ID in B. The next statement will copy #1234 into C. It doesn't do anything with the car--it just copies the ID. Then car #1234 will be painted yellow, then in the last statement, the color of car #1234 (i.e. yellow) will be used to paint car #4321. Note that while A and C are different variables, they both hold the same instance ID (#1234) and thus refer to the same car.
I cannot understand the output of the two sets of code snippets given below.
How don't really get the concept of shallow copy. How can it be explained?
Class:
public class Person : ICloneable
{
public string Name;
public int[] arr;
public object Clone()
{
return this.MemberwiseClone();
}
}
Code Snippet 1:
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "Name1";
p1.arr = new int[5] {1,2,3,4,5 };
Person p2 = (Person)p1.Clone();
p1.Name = "Name2";
p1.arr[0] = 11;
Console.WriteLine(p2.Name);
Console.WriteLine(p2.arr[0].ToString());
Console.Read();
}
Output:
Name1
11
Doubt: Isn't string a reference type. Then why p2.Name is printed as "Name1" in snippet 1
Code Snippet 2:
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "Name1";
p1.arr = new int[5] { 1, 2, 3, 4, 5 };
Person p2 = (Person)p1.Clone();
p1.Name = "Name2";
p1.arr = new int[5] { 11, 12, 13, 14, 15 };
Console.WriteLine(p2.Name);
Console.WriteLine(p2.arr[0].ToString());
Console.Read();
}
Output:
Name1
1
The int[] array in your example is a reference type. That means, that both p1.arr and p2.arr point to the same array in memory.
If you change the value of the first index of p1.arr, this means that the value of the first index of p2.arr is also changed. Hence, the behaviour in code snippet 1.
The difference in the second code snippet, is that you change the reference to the array of p1. Now, p1.arr is a reference to a new object. p2.arr still holds a reference to the 'original' array. Thus, printing p2.arr[0] prints 1.
EDIT:
To hopefully take away some doubt, maybe it is clearer if you remember that typing:
p1.Name = "Name2";
is actually:
p1.Name = new String("Name2");
This is exactly the same as with your int[] array. You are not changing the value of p1.Name, you are creating a new string object, and changing the reference of p1.Name to this new string object. p2.Name still holds its own reference to the 'original' string object, namely 'Name1'. By changing the reference of p1.Name, that reference does not change.
From http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx
The MemberwiseClone method creates a shallow copy by creating a new object, and then copying the nonstatic fields of the current object to the new object. If a field is a value type, a bit-by-bit copy of the field is performed. If a field is a reference type, the reference is copied but the referred object is not; therefore, the original object and its clone refer to the same object.
To alleviate your doubt in the original question.
String is indeed a reference type, The thing to remember is that what you are doing to the array and what you are doing to the string are not the same.
p1.Name = "Name2"; // new String -equivalent to p1.Name = new string("Name2")
p1.arr[0] = 11; //updated array element
For the array you are are changing the data in piece of memory referenced. For the String you are creating a new string ( at a new memory location) and making p1.Name ,the reference, point to that newly allocated memory. p2.Name (which is a different reference) remains pointing at the original memory location where the characters "Name1" are stored
As an aside because of the immutability of string there is no way a change to p1.Name will show up in p2.Name. Any attempt to change the string ,such as string.replace, will create a new string in memory.
hope that helps.
Pls see inline comments:
static void Main(string[] args)
{
Person p1 = new Person();
p1.Name = "Name1";
p1.arr = new int[5] {1,2,3,4,5 };
Person p2 = (Person)p1.Clone();
p1.Name = "Name2"; //Now p1.Name points to a new memory location
//But p2.Name is still pointing to the location p1.Name had
// originally pointed to.
p1.arr[0] = 11; //here p1.arr and p2.arr are pointing to the same place
//So since you are changing the value of one location it gets
//reflected in both
Console.WriteLine(p2.Name); //Prints Name1
Console.WriteLine(p2.arr[0].ToString()); //Prints 11
Console.Read();
}
In the second snippet when you say
p1.arr = new int[5] { 11, 12, 13, 14, 15 };
p1.arr is made to point to an entirely new location. (like what happens when you do p1.Name = "Name2") So it is not getting reflected on p2.arr which is still pointing to the same place p1.arr was previously pointing to. (i.e to the array {1,2,3,4,5})