简体   繁体   中英

Value behavior of data structure (reference type) inside struct c#

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.)?

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, ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM