Passing structure to a procedure as ByValue, however the procedure changes original structure values

1.2k Views Asked by At

I want to ask something about VB.NET code. I'm suffering from this problem a lot. Please answer me if you know the solution of my question or give me some comments.


'Declare a structure "Gene"
    Public Structure Gene
    Dim Seq() As Integer
    End Structure

'Here is a procedure, it changes original value.
        Public Sub Mutation(ByVal OriginalGene As Gene, ByRef TargetGene As Gene)
        Dim P1 As Integer
        Dim P2 As Integer
        Dim Temp As Integer

        P1 = Int((N_Jobs - 1 + 1) * Rnd(RndNum) + 0)
        P2 = Int((N_Jobs - 1 + 1) * Rnd(RndNum + 1) + 0)

        TargetGene.Seq = OriginalGene.Seq

        Temp = TargetGene.Seq(P1)
        TargetGene.Seq(P1) = TargetGene.Seq(P2)
        TargetGene.Seq(P2) = Temp
        End Sub
3

There are 3 best solutions below

1
On

If the length of the sequence is fixed, you may be able to use a fixed array declaration to hold it, but fixed arrays require "unsafe" code which may be problematic in some cases.

Otherwise, there's really no nice way for a structure to encapsulate a non-fixed collection with value semantics. If one wishes to have the structure convey the illusion of encapsulating a collection with mutable value semantics, one must have the structure hold a private reference to an array or other data type which will never change once a reference has been stored into a struct field. If the structure is asked to change the contained data, it must create a new object which encapsulates the changed data. Note that any changes which are ever going to be made must take place before storing the reference.

A further difficulty with having structures provide mutable value semantics is that there is no means via which instance methods on a structure can indicate whether they may be safely employed on read-only structure instances. In reality, no structure methods or properties may be used on read-only structure instances, but if C# sees, e.g.

 someObject.propertyOfStructType.doSomething();

it will silently rewrite the code as

 var temp = someObject.propertyOfStructType;
 temp.doSomething();

Sometimes that substitution is safe. Sometimes it isn't. Although it should be simple for a compiler to check whether a method is tagged with a "not for use on read-only structures" attribute and refuse to employ the above substitution with such methods, no attribute for such purpose has been documented. Consequently, if one wants to provide a safe means of e.g. copying a range of values from an array into a value-array type, one must write and invoke the method as something like:

public static int LoadFromEnumerable(ref ValueArray<T> it, int startIndex, IEnumerable<T> source);

int numElementsAdded = ValueArray<int>.LoadFromEnumerable(ref myValueArray, 4, myIntegers);

as opposed to the much cleaner-looking

public int LoadFromEnumerable(int startIndex, IEnumerable<T> source);

myValueArray.LoadFromEnumerable(4, myIntegers);
0
On

You need to implement the copy of the structure, try something like:

    Structure Gene
    Dim Seq() As Integer
    Public Function Clone() As Gene
        Dim mySt As Gene
        Array.Copy(Me.Seq, mySt.Seq, Me.Seq.Length)
        Return mySt
    End Function
End Structure

And then instead of:

TargetGene.Seq = OriginalGene.Seq

use

TargetGene.Seq = OriginalGene.Clone()
5
On

It's not actually changing the structure. All the structure contains is a pointer to an array. The reference to the array is always the same even though you can have an unlimited number of copies of the structure.


Example of immutable Gene class:

Public Class Gene
    Private _sequence() As Integer
    Public Sub New(sequence() As Integer)
        _sequence = sequence
    End Sub
    Public Function GetSequence() As Integer()
        Return _sequence.Select(Function (x) x).ToArray()
    End Function
    Public Function Mutate() As Gene
        Dim sequence() As Integer = Me.GetSequence()

        Dim P1 As Integer
        Dim P2 As Integer
        Dim Temp As Integer

        P1 = Int((N_Jobs - 1 + 1) * Rnd(RndNum) + 0)
        P2 = Int((N_Jobs - 1 + 1) * Rnd(RndNum + 1) + 0)

        TargetGene.Seq = OriginalGene.Seq

        Temp = sequence(P1)
        sequence(P1) = sequence(P2)
        sequence(P2) = Temp

        Return New Gene(sequence)
    End Function
End Class