locked
Comment faire simplement la copie d'une structure en VbNet ? RRS feed

  • Question

  • Bonjour,

    Voici le fonctionnement de mon programme: Le programme stocke ses données dans une Structure globale (que l'on va appeler guConfig). A chaque fois que j'ouvre un formulaire, je recopie le données dans une Stucture locale temporaire (que l'on va appeler muConfig). La validation dui formulaire provoque la recopie de la variable locale muConfig dans guConfig (tandis que l'annulation préserve guConfig).

    EN Vb6, je faisais tout simplement muConfig = guConfig pour recopier les données de guConfig dans la variable locale muConfig.

    EnVbNet, ça ne fonctionne pas: Les tableaux contenus danas guConfig sont automatiquement passés par référence (et donc guConfig se trouve modifié quand je modifie muConfig, ce qui est l'inverse du fonctionnement désiré).

    Quelqu'un peut-il me dire comment procéder en VbNet ?

    Voici un exemple de code pour illustrer mon propos:

    'Valeurs de ma globale guConfig
    guConfig.strConfigNom = "ConfigNomOriginal"
    guConfig.LCP(2).strLCPNom = "LCPNomOriginal"
    
    'Recopie de la globale guConfig vers la locale muConfig
    muConfig = guConfig
    
    'Modification de la variable locale muConfig
    muConfig.strConfigNom = "ConfigNomModifie"
    muConfig.LCP(2).strLCPNom= "LCPNomModifie"
    
    'PROBLEME EN CONSULTANT guConfig ! 
    'guConfig.strConfigNom est bien préservé à "ConfigNomOriginal"
    'mais guConfig.LCP(2).strLCPNom a été modifié à "LCPNomModifie"

    Merci d'avance à ceux qui pourront m'aider !

    Projet volumineux en cours de migration de Vb6 à VbNet2008 Visual Studio 2008 - VbNET/Vb6(COM)
    mercredi 24 février 2010 08:57

Réponses

  • Bonjour,

    efectivement cela risque de te faire pas mal de changement et les classes VB6 risquent de ne pas aimer. Pour une phase de transition et corriger le problème tu peux écrire une méthode clone sur ta structure ( il est même possible d'implémenter l'interface ICloneable ). Cette méthode te sera utile par la suite quand tu pourras utiliser des classes et liste génériques à la place de structures et tableaux et te permettra de faire la transition en douceur.
    Voici un exemple qui reproduit ton problème et propose une solution "temporaire"

    Module Module1
    
        Sub Main()
    
            Dim struc As MyStruct
            struc.chaine = "origine"
            struc.value = 0
            struc.tab = New MyStructFille(0) {}
            struc.tab(0).chainefille = "origine"
    
            Dim strucCopy As MyStruct
    
            'ref objet de départ mis à jour
            'strucCopy = struc
    
            'ref objet de départ non mis à jour
            strucCopy = struc.Copy()
    
            strucCopy.chaine = "modif"
            strucCopy.value = 1
            strucCopy.tab(0).chainefille = "modif"
            strucCopy.tab(0).intfille = 1
    
            Console.WriteLine(struc.chaine)
            Console.WriteLine(struc.value)
            Console.WriteLine(struc.tab(0).chainefille)
            Console.WriteLine(struc.tab(0).intfille)
    
            Console.WriteLine("----------")
    
            Console.WriteLine(strucCopy.chaine)
            Console.WriteLine(strucCopy.value)
            Console.WriteLine(strucCopy.tab(0).chainefille)
            Console.WriteLine(strucCopy.tab(0).intfille)
    
            Console.ReadLine()
    
        End Sub
    
    End Module
    
    
    Structure MyStructFille
        Public chainefille As String
        Public intfille As Integer
    
        Public Function Copy() As MyStructFille
            Return Me.MemberwiseClone
        End Function
    
    End Structure
    
    Structure MyStruct
        Public value As Decimal
        Public chaine As String
        Public tab As MyStructFille()
    
    
        Public Function Copy() As MyStruct
    
            'shallow copy
            Dim myCopy As MyStruct = Me.MemberwiseClone()
    
            'copy ref
            Dim list As List(Of MyStructFille) = New List(Of MyStructFille)()
            For Each strucFille As MyStructFille In Me.tab
                list.Add(strucFille.Copy())
            Next
    
            myCopy.tab = list.ToArray()
    
            Return myCopy
    
        End Function
    
    End Structure


    • Marqué comme réponse Alex Petrescu vendredi 26 février 2010 12:49
    jeudi 25 février 2010 10:05

Toutes les réponses

  • Bonjour,

    quand tu parles de structure s'agit-il réellement d'une "structure" ou d'une "class".
    Dans le cas des structures celle ci sont gérées comme des types valeurs, et donc lors de l'affectation d'une variable il y a copie des valeurs de l'objet source.

    D'après le problème que tu rencontres cela laisse supposer qu'il s'agit d'une classe, car il y a copie de la référence
    Dans ce cas tu peux implémenter l'interface System.ICloneable qui présente la méthode Clone.
    C'est au sein de cette méthode que tu dois alors prévoir la mécanique de copie des références.

    Cordialement
    mercredi 24 février 2010 09:46
  • Merci nikho,

    Ta réponse me laisse perplexe ????? Et j'aimerais que tu ais raison !
    Je lis bien dans le cours de P.Lasserre que les Structures sont de type 'valeur' et qu'elles contiennent directement les valeurs de données, tandis qu'une Classe contient une référence aux données...
    Le même cours spécifie aussi que les tableaux sont de type 'référence'...

    Dans mon cas, il s'agit bien de structures, mais ma Structure contient bien sur des tableaux, ainsi que des tableaux de structures.

    Tu vois dans mon exemple que guConfig.strConfigNom est bien préservé (ce qui je pense ne serait pas le cas s'il s'agissait d'une classe), MAIS  guConfig.LCP(2).strLCPNom N'EST PAS PRESERVé.

    Dans cet exemple guConfig est déclaré "As Config" sachant que Config est une structure publique déclarée "Public Structure Config".
    La structure Config contient un tableau LCP() déclaré "As TabLCP" sachant que TabLCP est également une structure publique...

    Je ne m'en sors pas...
    Comment "cloner" ma structure guConfig ?
    Si tu veux, je peux te faire un petit projet test afin de te l'envoyer.

    HELP !





    Projet volumineux en cours de migration de Vb6 à VbNet2008 Visual Studio 2008 - VbNET/Vb6(COM)
    mercredi 24 février 2010 10:47
  • Je n'avais pas regardé avec assez d'attention ta structure et le fait qu'elle contenait des tableaux.
    Dans ton cas la structure contient des membres de type référence et donc tu t'exposes à ce genre de problème car tu obtiens un objet de type valeur qui contient un objet de type référence. Lors de la copie c'est bien la valeur de la référence qui est copiée : résultat les modifications dans muConfig sont présentes dans guConfig car les 2 tableaux pointent vers la même référence.

    Cela indique que tu devrais utiliser une classe et non une structure pour ton objet guConfig. Les structures sont en générale réservées au objet de petites tailles contenant essentiellement d'autres types valeurs ( exemple : System.Drawing.Point )

    En passant ton objet sous forme de classe et en implémentant une méthode Clone via l'interface tu pourras écrire : muConfig = guConfig.Clone()
    Attention pour ne pas retomber sur le même problème de référence tu dois effectuer une "copie profonde" ( deep copy voir ici ).

    Cordialement
    mercredi 24 février 2010 13:15
  • Il va falloir que j'étudie cette histoire de deep copy...

    Pour commencer, je dois transformer toutes mes structures en classes (une bonne cinquantaine de classes imbriquées)... et je ne vois pas trop comment faire.
    Cela va-t'il modifier les déclarations OMNIPRESENTES de mes variables dans tout mon programme ?
    Cela va-t'il empêcher de fonctionner mes classes VB6 qui utilisent ces variables en paramètre ?
    Projet volumineux en cours de migration de Vb6 à VbNet2008 Visual Studio 2008 - VbNET/Vb6(COM)
    mercredi 24 février 2010 14:47
  • Bonjour,

    efectivement cela risque de te faire pas mal de changement et les classes VB6 risquent de ne pas aimer. Pour une phase de transition et corriger le problème tu peux écrire une méthode clone sur ta structure ( il est même possible d'implémenter l'interface ICloneable ). Cette méthode te sera utile par la suite quand tu pourras utiliser des classes et liste génériques à la place de structures et tableaux et te permettra de faire la transition en douceur.
    Voici un exemple qui reproduit ton problème et propose une solution "temporaire"

    Module Module1
    
        Sub Main()
    
            Dim struc As MyStruct
            struc.chaine = "origine"
            struc.value = 0
            struc.tab = New MyStructFille(0) {}
            struc.tab(0).chainefille = "origine"
    
            Dim strucCopy As MyStruct
    
            'ref objet de départ mis à jour
            'strucCopy = struc
    
            'ref objet de départ non mis à jour
            strucCopy = struc.Copy()
    
            strucCopy.chaine = "modif"
            strucCopy.value = 1
            strucCopy.tab(0).chainefille = "modif"
            strucCopy.tab(0).intfille = 1
    
            Console.WriteLine(struc.chaine)
            Console.WriteLine(struc.value)
            Console.WriteLine(struc.tab(0).chainefille)
            Console.WriteLine(struc.tab(0).intfille)
    
            Console.WriteLine("----------")
    
            Console.WriteLine(strucCopy.chaine)
            Console.WriteLine(strucCopy.value)
            Console.WriteLine(strucCopy.tab(0).chainefille)
            Console.WriteLine(strucCopy.tab(0).intfille)
    
            Console.ReadLine()
    
        End Sub
    
    End Module
    
    
    Structure MyStructFille
        Public chainefille As String
        Public intfille As Integer
    
        Public Function Copy() As MyStructFille
            Return Me.MemberwiseClone
        End Function
    
    End Structure
    
    Structure MyStruct
        Public value As Decimal
        Public chaine As String
        Public tab As MyStructFille()
    
    
        Public Function Copy() As MyStruct
    
            'shallow copy
            Dim myCopy As MyStruct = Me.MemberwiseClone()
    
            'copy ref
            Dim list As List(Of MyStructFille) = New List(Of MyStructFille)()
            For Each strucFille As MyStructFille In Me.tab
                list.Add(strucFille.Copy())
            Next
    
            myCopy.tab = list.ToArray()
    
            Return myCopy
    
        End Function
    
    End Structure


    • Marqué comme réponse Alex Petrescu vendredi 26 février 2010 12:49
    jeudi 25 février 2010 10:05
  • Bonjour Alex,

    Ta solution est très intéressante.
    Merci beaucoup !!!

    Mais voilà, j'avais également imaginé quelque chose de similaire dans le principe, sauf que l'équivalent de tes fonctions "Copy" était externe aux structures. Il me faut à présent choisir la meilleure solution.

    En reprenant ton exemple, voici ma solution:

    Module Module1
    
        Sub Main()
    
            Dim struc As MyStruct
            struc.chaine = "origine"
            struc.value = 0
            struc.tab = New MyStructFille(0) {}
            struc.tab(0).chainefille = "origine"
    
            Dim strucCopy As MyStruct
    
            'ref objet de départ mis à jour
            'strucCopy = struc
    
            'ref objet de départ non mis à jour
            Call TransfertMyStruct(struc,strucCopy)
    
            strucCopy.chaine = "modif"
            strucCopy.value = 1
            strucCopy.tab(0).chainefille = "modif"
            strucCopy.tab(0).intfille = 1
    
            Console.WriteLine(struc.chaine)
            Console.WriteLine(struc.value)
            Console.WriteLine(struc.tab(0).chainefille)
            Console.WriteLine(struc.tab(0).intfille)
    
            Console.WriteLine("----------")
    
            Console.WriteLine(strucCopy.chaine)
            Console.WriteLine(strucCopy.value)
            Console.WriteLine(strucCopy.tab(0).chainefille)
            Console.WriteLine(strucCopy.tab(0).intfille)
    
            Console.ReadLine()
    
        End Sub
    
    
            Public Function TransfertMyStruct(ByVal MyStructSource As MyStruct, ByRef MyStructCible As MyStruct) As Boolean
    
                Dim inti As Integer
    
                On Error GoTo ErrTransfertMyStruct
    
                TransfertMyStruct = False
    
                 MyStructCible = MyStructSource 
    
                '====================================
                ' Clonage des éléments de la structure comportant des tableaux pour permettre la copie PAR VALEUR
                '====================================
                If Not MyStructSource .tab Is Nothing Then
                    MyStructCible.tab = MyStructSource.tab.Clone
                    'Si la structure MyStructFille contenait des tableaux, on aurait un autre appel similaire,
    'etc, etc...de même que tes fonctions Copy s'imbriquent...: For inti = 0 To MyStructSource.tab.GetUpperBound(0) Call TransfertMyStructFille(MyStructSource.tab(inti), MyStructCible.tab(inti)) Next inti End If TransfertMyStruct= True Exit Function ErrTransfertMyStruct: Call DisplayError(Err, "TransfertMyStruct") End Function End Module Structure MyStructFille Public chainefille As String Public intfille As Integer End Structure Structure MyStruct Public value As Decimal Public chaine As String Public tab As MyStructFille() End Structure

    Comme je trouve l'utilisation des fonctions dans les structure plus "propre" et plus "POO", j'ai réécris ma solution dans les structures, mais en continuant d'utiliser ".Clone" qui fait directement la Shallow Copy du tableau:

    Module Module1
    
        Sub Main()
    
            Dim struc As MyStruct
            struc.chaine = "origine"
            struc.value = 0
            struc.tab = New MyStructFille(0) {}
            struc.tab(0).chainefille = "origine"
    
            Dim strucCopy As MyStruct
    
            'ref objet de départ mis à jour
            'strucCopy = struc
    
            'ref objet de départ non mis à jour
            strucCopy = struc.Copy()
    
            strucCopy.chaine = "modif"
            strucCopy.value = 1
            strucCopy.tab(0).chainefille = "modif"
            strucCopy.tab(0).intfille = 1
    
            Console.WriteLine(struc.chaine)
            Console.WriteLine(struc.value)
            Console.WriteLine(struc.tab(0).chainefille)
            Console.WriteLine(struc.tab(0).intfille)
    
            Console.WriteLine("----------")
    
            Console.WriteLine(strucCopy.chaine)
            Console.WriteLine(strucCopy.value)
            Console.WriteLine(strucCopy.tab(0).chainefille)
            Console.WriteLine(strucCopy.tab(0).intfille)
    
            Console.ReadLine()
    
        End Sub
    
    End Module
    
    
    Structure MyStructFille
        Public chainefille As String
        Public intfille As Integer
    
        Public Function Copy() As MyStructFille
            Return Me.MemberwiseClone
        End Function
    
    End Structure
    
    Structure MyStruct
        Public value As Decimal
        Public chaine As String
        Public tab As MyStructFille()
    
    
        Public Function Copy() As MyStruct
    
            'shallow copy
            Dim myCopy As MyStruct = Me.MemberwiseClone
            Dim inti As Integer
    
                    If Not Me.tab Is Nothing Then
                        myCopy.tab = Me.tab.Clone
                        For inti = 0 To Me.tab.GetUpperBound(0)
                            'Si La structure tab contient des tableaux:
                            myCopy.tab(inti) = Me.tab(inti).copy
                        Next inti
                    End If
    
                    Return myCopy
    
        End Function
    
    End Structure
    


    Qu'en pense-tu ?

    Considérons les 3 solutions: la tienne, la mienne externe aux structures, et cette dernière interne aux structures avec l'utilisation de ".Clone"

    Je pense que tu seras d'accord avec moi pour éliminer la solution externe ?  Mais laquelle est la mieux entre ta copie avec "list.ToArray" ou la dernière solution avec ".Clone" ?

    J'attends avec impatience tes arguments ! A moins que les 2 solutions se valent...?


    Projet volumineux en cours de migration de Vb6 à VbNet2008 Visual Studio 2008 - VbNET/Vb6(COM)
    • Modifié Golard lundi 1 mars 2010 12:00 corrections...
    lundi 1 mars 2010 11:58
  • Bonjour Golard,

    je partage ton avis pour la solution externe.
    Pour les autres solutions je ne sais pas laquelle serait la plus performante. Dans tout les cas pour améliorer les choses il doit y avoir intérêt à allouer dès le départ la taille du tableau. Pour éviter les clones, ou le listToArray on pourrait écrire le code suivant :

    Dim myCopy As MyStruct = Me.MemberwiseClone()
    myCopy.tab = New MyStructFille(Me.tab.GetUpperBound(0)) {}
    
    'copy ref
    For inti = 0 To Me.tab.GetUpperBound(0)
         myCopy.tab(inti) = Me.tab(inti).Copy()
    Next inti
    
    Return myCopy

    Il faudrait faire des tests de performances pour en être plus sur...

    Nikho ( Alex avait validé la réponse )

    lundi 1 mars 2010 13:18