C
C#•3y ago
Angius

Since 'string' is a reference type, why doesn't the array update all elements with a modified string

It's more apt to say string is a value type stored on the heap
63 Replies
Angius
AngiusOP•3y ago
It is a reference type, but only due to the limitations of item size on the stack It implements pretty much the whole functionality of a value type
Pobiega
Pobiega•3y ago
strings are immutable in C#
triplemocha
triplemocha•3y ago
Interesting. I mainly tested this because later I'm going to be adding class types into an array like this, say 'class Wall'. If I use Fill, I'm concerned the array will point to just one wall instead of the intended several unique walls.
Pobiega
Pobiega•3y ago
correct, it would indeed fill it with the same reference you'll need to loop over the array and assign new Wall() instead
triplemocha
triplemocha•3y ago
I see. It's going to be a 1000x1000 array, but I guess it shouldn't take too long to loop through it and add new.
Pobiega
Pobiega•3y ago
it'll take a little bit, but not THAT long šŸ™‚
triplemocha
triplemocha•3y ago
I tried that, but ran into this issue:
public void FillClone(T value)
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = new T(); // I really want a clone of 'value', not the default constructor of T.
}
public void FillClone(T value)
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = new T(); // I really want a clone of 'value', not the default constructor of T.
}
Pobiega
Pobiega•3y ago
then you might need to implement a clone method for T there is an interface for it that you can restrict T to be ICloneable
triplemocha
triplemocha•3y ago
I just want it restricted in this method.
Pobiega
Pobiega•3y ago
well, where is T declared? on the type that has this method? it will need to be clonable for it to work with this method, so it shouldnt change anything
triplemocha
triplemocha•3y ago
I ran into this problem before and ended up doing a C++ project instead. So, I really want to understand this. Ah, so something like this?
class Wall : ICloneable
{
public bool isBreakable;

public Wall()
{
isBreakable = true;
}

public object Clone()
{
return this.MemberwiseClone();
}
}
class Wall : ICloneable
{
public bool isBreakable;

public Wall()
{
isBreakable = true;
}

public object Clone()
{
return this.MemberwiseClone();
}
}
Pobiega
Pobiega•3y ago
sure as long as Wall doesnt have any reference types at all in it, that will work if you have reference types, they need to be cloned individually. deep cloning can get pretty complicated.
triplemocha
triplemocha•3y ago
There will be reference types in the class.
Pobiega
Pobiega•3y ago
ouchies šŸ™‚ well, assuming you want those cloned too
triplemocha
triplemocha•3y ago
Eh, this is annoying coming from a C++ background.
Pobiega
Pobiega•3y ago
if you actually want them to point to the same object that value.X is, then its fine I dont see how this is different at all from cpp
triplemocha
triplemocha•3y ago
Maybe it's just the way I do it, without using 'new' much.
Pobiega
Pobiega•3y ago
public class MochaWorld
{
private ITile[] _world = new ITile[500];

public void Fill<T>(T value) where T : ITile, ICloneable<T>
{
for (int i = 0; i < _world.Length; i++)
{
_world[i] = value.Clone();
}
}
}
class Wall : ICloneable<Wall>, ITile
{
public bool isBreakable;

public Wall()
{
isBreakable = true;
}

public Wall Clone()
{
return (Wall)MemberwiseClone();
}
}

public interface ITile
{
}

public interface ICloneable<T>
{
T Clone();
}
public class MochaWorld
{
private ITile[] _world = new ITile[500];

public void Fill<T>(T value) where T : ITile, ICloneable<T>
{
for (int i = 0; i < _world.Length; i++)
{
_world[i] = value.Clone();
}
}
}
class Wall : ICloneable<Wall>, ITile
{
public bool isBreakable;

public Wall()
{
isBreakable = true;
}

public Wall Clone()
{
return (Wall)MemberwiseClone();
}
}

public interface ITile
{
}

public interface ICloneable<T>
{
T Clone();
}
something like this?
triplemocha
triplemocha•3y ago
Yeah, seems understandable and good to study from. Thanks. @Pobiega This comes up often:
class Wall : ICloneable<Wall>
class Wall : ICloneable<Wall>
Error: The non-generic type 'ICloneable' cannot be used with type arguments. Though this compiled fine. I changed Wall to object in Clone();
class Wall : ICloneable
{
public bool isBreakable;

public Wall()
{
isBreakable = true;
}

public object Clone()
{
return (Wall)this.MemberwiseClone();
}
}
class Wall : ICloneable
{
public bool isBreakable;

public Wall()
{
isBreakable = true;
}

public object Clone()
{
return (Wall)this.MemberwiseClone();
}
}
Pobiega
Pobiega•3y ago
yeah I introduced my own interface
public interface ICloneable<T>
{
T Clone();
}
public interface ICloneable<T>
{
T Clone();
}
since I didn't want to deal with object šŸ˜„
triplemocha
triplemocha•3y ago
Ah. I'm also confused with this one:
public void FillClone<T>(T value) where T : ICloneable<T>
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = value.Clone();
}
public void FillClone<T>(T value) where T : ICloneable<T>
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = value.Clone();
}
FillClone is in Array2D<T>, so adding <T> to FillClone is causing the issue here I think. It says Error: The non-generic type 'ICloneable' cannot be used with type arguments.
Pobiega
Pobiega•3y ago
one sec
public class Array2D<T> where T : ITile, ICloneable<T>
{

}
public class Array2D<T> where T : ITile, ICloneable<T>
{

}
just change Array2D to be like this then potentially you could remove the ITile restriction, but it will mean you can't say that your backing array is ITile I did it like this because I imagine you might want to store other things than Wall in here šŸ™‚
triplemocha
triplemocha•3y ago
My Mocha World is just walls. All walls. šŸ˜† I do want to keep Array2D generic enough so it's not just for my game project. So, I don't know if I want to say it's always a tile.
Pobiega
Pobiega•3y ago
sure thats fine then it will probably look like
public class Array2D<T> where T : ICloneable<T>
{
private T[] _world = new T[500];

public void Fill(T value)
{
for (int i = 0; i < _world.Length; i++)
{
_world[i] = value.Clone();
}
}
}
public class Array2D<T> where T : ICloneable<T>
{
private T[] _world = new T[500];

public void Fill(T value)
{
for (int i = 0; i < _world.Length; i++)
{
_world[i] = value.Clone();
}
}
}
triplemocha
triplemocha•3y ago
It worked šŸ™‚ Prior to your last post, I'll look into it.
map.FillClone(new Wall()); // breakable is true by default
map.Get(1, 1).isBreakable = false;
Console.WriteLine(map.Get(2, 2).isBreakable); // clone outputs true
map.FillClone(new Wall()); // breakable is true by default
map.Get(1, 1).isBreakable = false;
Console.WriteLine(map.Get(2, 2).isBreakable); // clone outputs true
Yeah, that's what I did to get it to run. I don't know if I need two separate ones, but one just clones it instead of a reference copy.
public void Fill(T value)
{
Array.Fill(m_array, value);
}

public void FillClone(T value)
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = (T)value.Clone();
}
public void Fill(T value)
{
Array.Fill(m_array, value);
}

public void FillClone(T value)
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = (T)value.Clone();
}
Oh, I guess there could be a second argument to turn both into one. ("cloneMe")
Pobiega
Pobiega•3y ago
nah this is fine. if this is for some kinda library that you plan on reusing, you want a nice API to work with I'd slap some /// comments on top of each method
triplemocha
triplemocha•3y ago
I saved you from my comments, been removing them. I go a bit crazy with those.
Pobiega
Pobiega•3y ago
šŸ˜„
triplemocha
triplemocha•3y ago
What I meant was this:
public void Fill(T value, bool clone)
{
if (clone)
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = (T)value.Clone();
}
else
{
Array.Fill(m_array, value);
}
}
public void Fill(T value, bool clone)
{
if (clone)
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = (T)value.Clone();
}
else
{
Array.Fill(m_array, value);
}
}
Pobiega
Pobiega•3y ago
please don't bool parameters like that is kind of an anti-pattern
triplemocha
triplemocha•3y ago
Ah. Okay.
triplemocha
triplemocha•3y ago
public void Fill(T value, bool clone, bool cloneButLast, bool cloneButFirst, bool pleaseClone)
Pobiega
Pobiega•3y ago
catree
triplemocha
triplemocha•3y ago
Say later my Wall class has an Items container. It holds item ids. List<int> m_items = new List<int>();
Does that need any special treatement for cloning the wall, too?
Pobiega
Pobiega•3y ago
if you want each of those walls to have a unique list, then yes
triplemocha
triplemocha•3y ago
Each will need a unique list.
Pobiega
Pobiega•3y ago
MemberwiseClone() returns a shallow copy so you'll need to do something like...
triplemocha
triplemocha•3y ago
class Wall : ICloneable
{
public bool m_isBreakable;
public List<int> m_Items = new List<int>();

public Wall()
{
m_isBreakable = true;
}

public object Clone()
{
return this.MemberwiseClone();
}
}
class Wall : ICloneable
{
public bool m_isBreakable;
public List<int> m_Items = new List<int>();

public Wall()
{
m_isBreakable = true;
}

public object Clone()
{
return this.MemberwiseClone();
}
}
Something like this.
Pobiega
Pobiega•3y ago
class Wall : ICloneable
{
public bool m_isBreakable;
public List<int> m_Items = new List<int>();

public Wall()
{
m_isBreakable = true;
}

public object Clone()
{
var copy = (Wall)MemberwiseClone();
copy.m_Items = new();
return copy;
}
}
class Wall : ICloneable
{
public bool m_isBreakable;
public List<int> m_Items = new List<int>();

public Wall()
{
m_isBreakable = true;
}

public object Clone()
{
var copy = (Wall)MemberwiseClone();
copy.m_Items = new();
return copy;
}
}
would work, but would also not let you actually copy the list, a clone would always have an empty list welcome to the weird world of "deep cloning" you see, the problem just grows for each level of reference types you have since its a list of ints, its not too bad here
public object Clone()
{
var copy = (Wall)MemberwiseClone();
copy.m_Items = new();
copy.m_Items.AddRange(m_Items);

return copy;
}
public object Clone()
{
var copy = (Wall)MemberwiseClone();
copy.m_Items = new();
copy.m_Items.AddRange(m_Items);

return copy;
}
triplemocha
triplemocha•3y ago
I see, that's not too bad.
Pobiega
Pobiega•3y ago
yeah, but if it was a list of Item... you'd have to deepclone those too and it just keeps going
triplemocha
triplemocha•3y ago
I plan for Items to be a static class, which holds a list of type Item. To create a new item, it will be int myNewItemID = Items::create("genericItemLoadTag"); This saves the actual object into Items::m_items. But it returns the generated id. So, I guess in this case it'd need to store a clone of it. Most generated objects are stored in a lookup map table and is referenced by id. Luckily there's nothing further to clone with Item. I keep the data simple in it. For C#, a thought is maybe Items could be struct so it's a value type. @Pobiega Would changing all of this to struct be a good idea? I realized in C++ I was doing bitwise copies.
Pobiega
Pobiega•3y ago
ĀÆ\_(惄)_/ĀÆ this is out of my area of expertise
triplemocha
triplemocha•3y ago
Never used structs?
Pobiega
Pobiega•3y ago
not never, but very rarely I'm an enterprise web developer šŸ˜› but sure, a record struct seems like a good idea give that a spin
triplemocha
triplemocha•3y ago
Ah okay. In Unity they use structs for vectors, because the garbage collector is just for classes. So bitwise copies are usually faster. I did hear in C# MS doesn't recommend structs for anything over 16kb, which seems silly. I think it's just for performance reasons. Structs are like ints, they are value types that store data like classes do, but each have their pros/cons. Anyway, I was thinking that could be an option for my Items class and forget about any deep cloning concerns.
Pobiega
Pobiega•3y ago
well, List is a reference type, whatever you do so you'll still need the explicit list cloning behaviour
triplemocha
triplemocha•3y ago
Yeah, that can get confusing with classes and structs all working together. I heard that before generics existed, structs would get casted to classes šŸ˜† @Pobiega Is there a solution to this? Before we had class Array2D<T> where T : ICloneable but that worked for Wall. If I change the type to int it starts complaining about ICloneable.
Pobiega
Pobiega•3y ago
hm.. one way would be to make FillClone an extensionmethod, but then the _world array needs to be public
triplemocha
triplemocha•3y ago
It seems like this should work:
class Array2D<T> where T : INumber<T>, ICloneable
class Array2D<T> where T : INumber<T>, ICloneable
If it's a number or cloneable. In my perfect world, I guess.
Pobiega
Pobiega•3y ago
thats not how constraints work they are not "or", they are "and"
triplemocha
triplemocha•3y ago
That worked...
class Array2DExt<T> : Array2D<T> where T : ICloneable
{
/// <summary>
/// Fills the array with a given value.
/// </summary>
/// <param name="value">The value used to fill the array.</param>
public void FillClone(T value)
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = (T)value.Clone();
}
}
class Array2DExt<T> : Array2D<T> where T : ICloneable
{
/// <summary>
/// Fills the array with a given value.
/// </summary>
/// <param name="value">The value used to fill the array.</param>
public void FillClone(T value)
{
for (int i = 0; i < GetSize(); i++)
m_array[i] = (T)value.Clone();
}
}
Pobiega
Pobiega•3y ago
that... works. its a bit awkward I guess
triplemocha
triplemocha•3y ago
I thought that was a method extension hah. It's been a while. I guess that's more inheritance.
Pobiega
Pobiega•3y ago
thats exactly what it is extension method would be to make the class static, and move the T to FillClone and pass a this Array2D<T> self argument as the first thing
static class Array2DExt
{
/// <summary>
/// Fills the array with a given value.
/// </summary>
/// <param name="value">The value used to fill the array.</param>
public static void FillClone<T>(this Array2D<T> self, T value) where T : ICloneable
{
for (int i = 0; i < self.GetSize(); i++)
self.array[i] = (T)value.Clone();
}
}
static class Array2DExt
{
/// <summary>
/// Fills the array with a given value.
/// </summary>
/// <param name="value">The value used to fill the array.</param>
public static void FillClone<T>(this Array2D<T> self, T value) where T : ICloneable
{
for (int i = 0; i < self.GetSize(); i++)
self.array[i] = (T)value.Clone();
}
}
as I said, the problem is the visibility of m_array
triplemocha
triplemocha•3y ago
That worked. I used Array2DExt.FillClone<Wall>(map, new Wall());
Pobiega
Pobiega•3y ago
right, thats not how you call an extension method šŸ˜„ you'd call it like map.FillClone(new Wall());
triplemocha
triplemocha•3y ago
Well, thanks. A bit of a pain but at least it's a sharpppp pain. Ah Umm... it can't find FillClone. Assuming it's like
Array2D<Wall> map = new Array2D<Wall>();

map.Init(10, 10);
map.FillClone(new Wall());
Array2D<Wall> map = new Array2D<Wall>();

map.Init(10, 10);
map.FillClone(new Wall());
Ah, forgot the 'this' in FillClone<T>() It works... like an extended method of Map.
Pobiega
Pobiega•3y ago
thats why they are called extension methods šŸ™‚
triplemocha
triplemocha•3y ago
I never did one of those before. Horray.
Pobiega
Pobiega•3y ago
its just a static method, but it "behaves" as if it was a real part of the class its very idiomatic in modern c# to the point where we often add extension methods for our own types
triplemocha
triplemocha•3y ago
Well, thanks for the help. I think I won't be running away this time, now that I got a better understanding of "the clone wars."

Did you find this page helpful?