-1

As far as I got to understand Structs are value types which means they are copied when they are passed around. So if you change a copy you are changing only that copy, not the original and not any other copies which might be around.

However, you might have a struct with a list of classes, of which you want value behaviour, for example:

private struct InitData {
    public string name;
    public float initialSpeed;
    public bool loop;
    public List<Settings> settings;
}

Settings is a class with a lot of simple types, that is a class due to need of reference behaviolur.

For my case I need to keep the initial value of a class, for which I need this struct to keep the data.

Simple types behave as expected, and they are not modified when I set the values from this struct, not so with the List<Settings> Settings that changes values, because behaves as a reference type although it is inside a struct.

So I have to do to my _initData struct instance and to my List<Settings> _settings instance to break with the reference type behaviour of the list.

_settings = _initData.settings;
//renew instace to have value behaviour:
_initData.nodeSettings = new List<Settings>();
_initData.nodeSettings = (List<Settings>)DeepCopy(_settings.ToList());

So, as it does not seem very appropiate to generate a new instance of the list every single time, is there any way to achieve value behaviour inside a struct for a data structure to handle many instances or references with value type behaviour (meaning list, array, vector, etc.)?

rustyBucketBay
  • 4,320
  • 3
  • 17
  • 47
  • *"feel free to ask any question if interested in the topic"* - [why struct](https://stackoverflow.com/q/85553/1997232)? – Sinatr Aug 11 '20 at 11:18
  • because when I reset de class values, I will set my class values to the struct values, and I dont want a reference to the values hold, I want the values themselves because then I will change the class values again and reset to the initial as many times I want. Does that make sense? – rustyBucketBay Aug 11 '20 at 11:20
  • 2
    You should start by avoiding mutable value types to begin with. That said, a reference type member of a struct is still a reference type and will behave as such. – InBetween Aug 11 '20 at 11:20
  • so that is the essence of my question, what If you what a data structure holding many intances of some class behave as value type. Is that even possible? So create a new instance of the class every single time you want to "break the reference" is the best approach? – rustyBucketBay Aug 11 '20 at 11:22
  • 1
    A value type behaves like a value type. A reference type behaves as a reference type. You can't change that. If you want a reference type that is a memeber of a value type to behave like a value type,that is not possiblet. But if your value type is immutable this would not be an issue. My recommendation is, if you use value types, make them immutable. If you can't then use reference types unless there are very good performance reasons that make using value types necessary. – InBetween Aug 11 '20 at 11:24
  • *"Does that make sense?"* - nope. Use `class`, pass reference to its instance around, clone it if you need to. – Sinatr Aug 11 '20 at 11:25
  • I think that the flaw of my code is that I do _initData.nodeSettings = new List(); where the ne is not needed, and only renewing the struct field with a new clone is enough (_initData.nodeSettings = (List)DeepCopy(_settings.ToList());) – rustyBucketBay Aug 11 '20 at 11:39
  • So, I understood that the value behaviour of reference types is not possible in structs, and If you want such, you can renew the value of the ref type field every single time you need it to break the reference behaviour. In my modest opinion, if c# has such a defined value/ref behaviour depending on the type, it would be desirable to have value behaviour for some kind of data structure. – rustyBucketBay Aug 11 '20 at 11:44

1 Answers1

1

As mentioned by @InBetween, if a struct containing a reference is copied, only the reference is copied, not the object the reference refers to.

Mutable structs are widely considered evil, so it is a good idea to avoid them in general. If a immutable struct contains a reference to a mutable object it will behave just like any other reference that is passed around, i.e. when you mutate the object you must consider if the object is shared, and if so, if you want to mutate the object, or mutate a copy.

A nice way to avoid any problems with this is ensure the objects are immutable all the way down, so that any change requires creating a copy. Example using System.Collections.Immutable library:

public class Settings
{
    //Should only contain immutable fields
    public int MyValue { get; }

    public Settings(int myValue) => MyValue = myValue;

    // Can contain helpers to create a mutated object
    public Settings WithMyValue(int newMyValue) => new Settings(newMyValue);
}
public struct InitData
{
    public string Name { get; }
    public float InitialSpeed { get; }
    public bool Loop { get; }
    public ImmutableList<Settings> Settings { get; }

    public InitData(string name, float initialSpeed, bool loop, ImmutableList<Settings> settings)
    {
        this.Name = name;
        this.InitialSpeed = initialSpeed;
        this.Loop = loop;
        this.Settings = settings;
    }

    public InitData(string name, float initialSpeed, bool loop, IEnumerable<Settings> settings)
        : this(name, initialSpeed, loop, settings.ToImmutableList())
    { }

    public InitData MutateSettings(Func<ImmutableList<Settings>, ImmutableList<Settings>> mutator) 
        => new InitData(Name, InitialSpeed, Loop, mutator(Settings));
}

Another alternative, as you mention, is to create deep copies, example:

public class Settings
{
    public int MyValue { get; set; }
    public Settings(int myValue) => MyValue = myValue;
    public Settings Clone() => new Settings(MyValue);
}
public struct InitData
{
    public string Name { get; }
    public float InitialSpeed { get; }
    public bool Loop { get; }
    public List<Settings> Settings { get; }

    public InitData(string name, float initialSpeed, bool loop, List<Settings> settings)
    {
        this.Name = name;
        this.InitialSpeed = initialSpeed;
        this.Loop = loop;
        Settings = settings;
    }

    public InitData Clone() => new InitData(Name, InitialSpeed, Loop, Settings.Select(s => s.Clone()).ToList());
}

Since InitData is shallow-immutable it does not matter if it is a value or reference type, but since it is not deep-immutable you need to clone it whenever you want a unchanging copy.

It can be rather difficult to create a method that transparently creates deep copies. The runtime does not keep know if objects are deep-immutable or not, so it would need to copy all fields, and there is a risk you might copy much more than you intended. You would also need some way to handle circular references. So I'm not aware of any c-style language that does has this.

There is a method that uses serialization, like BinaryFormatter, for deep copies, but I would not recommend it.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Thanks for such detailed answer. 2 comments. 1.-Deep copying is the first thing I considered, because I wanted to "bulk deep copy" a bigger class holding my example, but some unity classes are not serializable, so I had to narrow that down. 2.-The small inconvenient for the 1st approach in my case is that as my settings class is quite big, I would need methods to change each of the properties, and overloaded constructors to maintain previous instance values for each of the property changes, to pass them in the new Settings construct. Anyway very cool approach to achieve value type behaviour. – rustyBucketBay Aug 12 '20 at 08:57
  • 1
    Yea, it is a problem if you want to mutate a large object. Records and With expressions are planned for c# 9 that should make this much easier, see https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ – JonasH Aug 12 '20 at 11:26