how does initializing structs work in c#? - c#

im currently going through a game tutorial in c# and since i dont know almost anything about the language simple stuff looks confusing.
in my class i have something like this:
[Serializable]
public struct TileStruct
{
public TileDataStruct[] Layer;
public byte Type;
public int Data;
public byte DirBlock;
}
public TileStruct[,] Tile;
first question is what does [,] mean here?
second, it is initialized like this:
Tile = new TileStruct[(byteValue), (byteValue)]
1) why are two values passed to TileStruct? Where do they end up?
2) why are the values passed in parentheses?

public TileStruct[,] Tile;
This part declares public variable named Tile. TileStruct[,] means this variable holds a reference to a two dimensional array which contains instances of TileStruct. You can read more about this here.
Tile = new TileStruct[(byteValue), (byteValue)]
This line creates new instance of two dimensional array I've mentioned earlier and assigns it to Tile variable. [(byteValue), (byteValue)] declares size of this array in each dimension. Those values are not passed to TileStruct in any way. There's no need for parentheses around byteValue and deleting them won't change anything.

Related

ScriptableObject resetting Array of PropertyDrawer to Length of Zero

I have a simple class called Behaviour that saves a string and a string-array. It looks like this:
[System.Serializable]
public class Behaviour {
public string Methodname;
public string[] Parameters;
}
As you might already expect, this class is to save a method to make it possible to plug methods into the unity inspector. Methodname is the name of the method plugged in, while Parameters are all the parameters for that method formatted in a way that a static utility class can read (these string get converted to objects to then be used as parameters. so a string of "i25" would get converted to an integer parameter of 25. the array is a string as object[] cannot be serialized and thusly not saved.
I will strip out all unnecessary logic and focus on what's going wrong. Lets just assume that we want to save a single integer into the first index of the parameters array. The PropertyDrawer of Behaviour would then look like this:
[CustomPropertyDrawer(typeof(Behaviour))]
public class BehaviourEditor : PropertyDrawer {
private Behaviour propertyReference;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
propertyReference = fieldInfo.GetValue(property.serializedObject.targetObject) as Behaviour;
// Check if the string array is null. This happens the very first time the scriptable object is created
if(propertyReference.Parameters == null) propertyReference.Parameters = new string[1];
// Check if the first index is null. In this simplified example also only happening the very first time.
if(propertyReference.Parameters[0] == null) propertyReference.Parameters[0] = "i0";
int value = (int)DataConverter.StringToObject(propertyReference.Parameters[index]);
value = EditorGUILayout.IntField(value);
propertyReference.Parameters[0] = DataConverter.ObjectToString(value);
}
}
The DataConverter simply converts from object to string and vice versa (so int n = 9 would become "i9" or "i255" would become object x = 255). All of this logic works.
Again to clarify: This is the PropertyDrawer of Behaviour. Behaviour is a private [SerializedProperty] within a ScriptableObject.
If the array is null, the if == null triggers and puts the array Parameters to the length of one. The later logic all works, we can assign values to the int field from the EditorGUILayout and the value there gets correctly saved into the array. All of that logic works.
BUT: something is changing the Parameters-Array of Behaviour. I believe that something to be the ScriptableObject. The very next frame, Parameters is no longer null (obviously) and we try to thusly access index position 0. Which results in an index out of range exception because something changed Parameters to new string[0]. They didn't set it to null, they set it's length to 0.
Why? What could possible trigger this logic? If I make Parameters a property and set a breakpoint into the set-method, noone else calls it but my own code above, yet the array still becomes length of 0. Any ideas?
Unity's Serializer indeed auto-initializes any fields of a serializable type such as a list or array or string etc! -> They will never be null, worst case they will be empty.
If you simply want defau values for your fields you can simply assign them in your class itself, no drawer needed for this:
[Serializable]
public class Behaviour
{
public string Methodname = "ExampleMethod";
public string[] Parameters = new string[1] { "i0" };
}
In general do not directly change values via the target in editor scripts!
This will cause you a lot of headaches regarding marking changed objects as dirty, serialize/save values correctly persistent, handle undo/redo etc
Always rather go through the SerializedProperty and use e.g.
var methodName = property.FindPropertyRelative(nameof(Behavior.Methodname));
var parameters = property.FindPropertyRelative(nameof(Behavior.Parameters));
and then do e.g.
EditorGUILayout.PropertyField(methodName);
and access and assign e.g.
parameters.arraySize = 1;
var parameter = parameters.GetElementAtIndex(0);
parameter.stringValue = "i0";
but as said that's only an example, you really want to simply put the default values in the class, not the drawer.
Also putting
EditorGUI.BeginProperty(position, label, property);
...
EditorGUI.EndProperty();
around your property drawer content is essential for the dirty state handling! See Property Drawers

Is there a reason for array declaration syntax in C#?

I was wondering what's the reason behind the syntax? I mean it's like this:
int[ , ] arrayName = new int[10,10];
Wouldn't it be simpler if we had something like:
int[10,10] arrayName ;
What caused the software architect to make such a decision?
It's because you can declare the variable in one scope, and initialize it in another (normally in a constructor).
Something like this:
public class Foo
{
private int[ , ] Bar; // Bar will be null until initialized
public Foo(int a, int b)
{
Bar = new int[a, b];
}
}
With int[10,10] arrayName; you are committing to the array size. What would happen if I did this?
int[10,10] arrayOne;
int[11,11] arrayTwo;
arrayOne = arrayTwo;
An array object is simply a pointer to a block of managed memory. Knowing this, arrayOne = arrayTwo would have to be technically legal.
Therefore, stating that it as an int[10,10] is actually a lie. It may start that way, but it could at any time be reassigned into an array of different dimensions.
This
int[ , ] arrayName = new int[10,10];
already initializes the variable in memory and instantiate it. Also, you could change the size of the array during runtime, for example
arrayName = new int[20,20]
On the other hand
int[10,10] arrayName ;
Will just declare the variable for the array. arrayNamethen is still null.
int[,] arrayName = new int[10,10];
Only the left hand side is declaring the array, the right hand side is initialising it. This is true for most (if not all) of variables in C#. For example
string test; //declaring
test = "test"; //initialising
string test = "test"; //declare and initialise

LINQ Select Many - Modify flattened collection

I have a class named ACTIVITY. This class contains a list of Laps, and each Lap has a collection of TRACPOINTS.
ACTIVITY --many--> LAPS ---many --> TRACPOINTS.
Whenever I fLatten the TRACPOINTS collection I get the list of all the TRACPOINTS. But when I modify those of course the originals don't get modified since it's a copy.
Is there any way that whatever change I made to the flattened tracpoints gets changed in the Tracpoints list for each lap?
As long as TRACPOINT is a struct, it is not possible in any reasonable way.
Whenever you assign a value of struct variable or field to another variable or field, its contents are copied. The same holds for passing it as a method argument or returning it from a method, its value is copied. This is value semantics [1]. Compare this to atomic types like int, which have value semantics too. You would probably expect the following code to print 2, not 3.
static function Change(int j) { j = 3; }
static void Main(string[] args) {
int i = 2;
Change(i);
System.Console.WriteLine(i);
}
If you do SelectMany, each value from the collection is probably assigned to some temporary local variable and then returned from the iterator (SelectMany), therefore it is copied and in fact possibly copied many times before it comes out from the iterator. So what you are updating is a copy of the struct. Like in the example, you're not changing variable i, but its copy stored in variable j.
This is why structs should be immutable. Instead of having properties with getters and setter in your struct, they should have only getters. For changing values of properties of a struct, you can implement methods that copy the whole original struct, change the value of the desired property and return the new struct instance. In fact, again, its copy will be returned. Example:
struct S {
int f;
public int F { get { return this.f; } }
public S SetF(int newVal) {
var s = new S();
s.f = newVal;
return s;
}
}
var x = new S();
x = x.SetF(30);
That said, it could be possible to achieve what you want with pointers and unsafe C#, but believe me, it will be way easier to change your structs to classes, so that they have reference semantics instead of value semantics, or keep them structs, but make them immutable and do not use Linq, but old school loops. If you want to use Linq for something like SelectMany in such scenario, you probably do not care about performance difference between structs and classes so much...
[1] http://msdn.microsoft.com/en-us/library/aa664472(v=vs.71).aspx

Structs: field must be fully assigned compiler error

I've declared the following struct:
struct StartPositions
{
public Vector2 pacman;
public Vector2[] ghosts;
// Constructor accepts a Vector2, and an array of Vector2's.
public StartPositions(Vector2 pacmanPosIn, Vector2[] ghostsPosIn)
{
pacman = pacmanPosIn;
for(int a=0;a<ghostsPosIn.Length;a++)
{
ghosts[a] = ghostsPosIn[a];
}
}
}
However I get a compiler error saying the ghosts field must be fully assigned. What I want to do is pass in a Vector2, and a Vector2 array when I create a StartPositions object - making a copy of that array.
How can I do this correctly?
You did not initialize the ghosts array. You need to add a call to new.
public StartPositions(Vector2 pacmanPosIn, Vector2[] ghostsPosIn)
{
pacman = pacmanPosIn;
ghosts = new Vector2[ghostsPosIn.Length];
....
}
And you can simplify the code by replacing the for loop with a call to Array.Copy().
Array.Copy(ghostsPosIn, ghosts, ghosts.Length);
You have to initialize your ghosts array first:
struct StartPositions
{
public Vector2 pacman;
public Vector2[] ghosts;
// Constructor accepts a Vector2, and an array of Vector2's.
public StartPositions(Vector2 pacmanPosIn, Vector2[] ghostsPosIn)
{
pacman = pacmanPosIn;
ghosts = new Vector2[ghostsPosIn.Length];
for(int a=0;a<ghostsPosIn.Length;a++)
{
ghosts[a] = ghostsPosIn[a];
}
}
}
You didn't initialize ghosts array.
public StartPositions(Vector2 pacmanPosIn, Vector2[] ghostsPosIn)
{
...
ghosts = new Vector2[ghostsPosIn.Length];
...
}
From C# Language Specification;
Actual array instances are created dynamically at run-time using the
new operator. The new operation specifies the length of the new array
instance, which is then fixed for the lifetime of the instance.
One annoying quirk in .net is that unless one is using "unsafe" code the concept of a value-type array does not exist. The struct as shown contains a position for the "pacman" and a reference to a mutable array that holds the positions of the ghosts. This is an evil combination, since the struct may appear to encapsulate the positions of the ghosts, but it does not. Thus, if one were to say:
StartPositions p1 = whatever();
... do some stuff
StartPositions p2 = p1;
p2.pacman.X += 3;
p2.ghosts[0].X += 3;
the code would add three to p2.pacman and p2.ghosts[0]; it would not affect p1.pacman.X but would add three to p1.ghosts[0]. Such behavior would likely cause confusion.
If your intention is that StartPositions will be read-only, it should probably never expose the ghosts array directly; instead, ghosts should be a property of type IList<Vector2>, and your constructor should set it to something like a new ReadOnlyList<Vector2> initialized with a copy of the passed-in positions. If it does that, then ghosts can simply be a read-only property that returns such positions.

How to make a shallow copy of an array?

I pass a two-dimensional array as a property to my user control. There I store this values in another two-dimensional array:
int[,] originalValues = this.Metrics;
Later, I change values in this.Metrics. But now if I retrieve values from originalValues, I get the changed values from this.Metrics. How do I make a copy of the elements of this.Metrics and don't just get the reference of the array?
I don't know where I got this from, but this works well for me.
public static class GenericCopier<T> //deep copy a list
{
public static T DeepCopy(object objectToCopy)
{
using (MemoryStream memoryStream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, objectToCopy);
memoryStream.Seek(0, SeekOrigin.Begin);
return (T)binaryFormatter.Deserialize(memoryStream);
}
}
}
You can clone an array, which makes a copy of it:
int[,] originalValues = (int[,])this.Metrics.Clone();
The crux of your problem is here:
There I store this values in another two-dimensional array
This is actually inaccurate. You are not creating a new array; you are setting your originalValues variable to the same array. For a more detailed explanation, see below.
The confusion expressed in the comments to Pieter's answer is due to some uncertainty surrounding the term "deep copy."
When it comes to copying objects, there's deep copying and shallow copying.
Deep copying involves making a copy of all the data belonging to an object, which means that if the object includes members which are themselves complex (for example, instances of user-defined reference types), those objects must be deep-copied as well (along with all of their members, and so on).
Shallow copying involves simply copying all of the fields from one object to another, which means that if the object includes reference types, only the references need to be copied (and so the copied references will be pointing to the same objects).
In the case of the code you've posted:
int[,] originalValues = this.Metrics;
... there's actually no copying of any objects at all. All you've done is copied a single reference, assigning the value of this.Metrics (a reference) to the variable originalValues (also a reference, to the very same array). This is essentially the same as a simple value assignment, like this:
int x = y; // No objects being copied here.
Now, the Array.Clone method makes, in fact, a shallow copy. But as Pieter pointed out, there's really no difference between a "shallow" or "deep" copy of an array of integers, since integers are not complex objects.
If you had something like this:
StringBuilder[,] builders = GetStringBuilders();
StringBuilder[,] builderCopies = (StringBuilder[,])builders.Clone();
..., you'd end up with a whole new array (a copy, yes), but one containing all of the same StringBuilder objects (so a shallow copy). This is where deep versus shallow copying comes into play; if you wanted a new array containing copies of all of the StringBuilder objects from builders, you'd need to make a deep copy.
If the object you are copying is an array, then you can use:
Array.Copy(sourceArray, destinationArray, sourceArray.Count)
This will give you a separate copy of the original array into your destination array.
If you want to deep copy an array of reference types, you can do this methodology:
Implement IClonable iterface for your class and do your deep copy of all value typed fields inside to another constructed object.
class A: ICloneable {
int field1;
public object Clone()
{
A a= new A();
//copy your fields here
a.field1 = this.field1;
...
}
}
Then you can do the actual copy using
A[] array1 = new A[]{....};
A[] array2 = array1.Select(a => a.Clone()).ToList();
IClonable is great but unless you IClonable every type in your top level cloned type, you end up with references, AFAIK.
Based on that, unless you want to walk the object and clone each object within, this seems the simplest approach.
It's simple and guarantees a clean break from references of deep objects in the original:
using Newtonsoft.Json;
private T DeepCopy<T>(object input) where T : class
{
var copy = JsonConvert.SerializeObject((T)input); // serialise to string json object
var output = JsonConvert.DeserializeObject<T>(copy); // deserialise back to poco
return output;
}
Usage:
var x = DeepCopy<{ComplexType}>(itemToBeCloned);
Where ComplexType is anything wanting a break from references.
It takes any Type in, stringifies it, then de-stringifies to a new copy.
Best use example:
If you've selected a complex type as a result of a lambda query and want to modify the result without affecting the original.
You can deepcopy a 1d array using LINQ.
var array = Enumerable.Range(0, 10).ToArray();
var array2 = array.Select(x => x).ToArray();
array2[0] = 5;
Console.WriteLine(array[0]); // 0
Console.WriteLine(array2[0]); // 5
With 2d array, this will not work because 2d array doesn't implement IEnumerable.
You need to create a new array. You then need to manually copy the value of each element into the new array. What you are doing in the example given is to create two array variables which both reference the same array.
The problem with the clone method is that it is a shallow copy. In this isntance, because you are using int, it does not mater. Howver, if you had an array of classes the definition of the ICLonable interface leaves it ambiguous as to how deep the clone will go.
Imagine if you had a class that has properties that are other classes which has properties that are other classes. The clonable interface does not state whether it will also clone the sub members or not. Moreover, many people have different views on what the expected behaviour is.
Hence this is why it is often recommended to define two interfaces, IShallowCopy and IDeepCopy.
Here is a fast solution that is almost similar than some answers here, but which mention MemberwiseClone.
I have POCO classes that only contains reference values.
public class MyPoco {
public int x { get; set; }
public int y { get; set; }
public int z { get; set; }
// Add a "Clone" method.
public MyPoco Clone() {
return (MyPoco)this.MemberwiseClone();
}
}
Then use LINQ to build a new array of clones:
var myClone = MyPocoArray.Select(x => x.Clone).ToArray();

Categories

Resources