Toon posts:

[.NET 2.0] ICloneable op generics collection implementeren

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik heb onderstaande classes/interfaces gedefinieerd:
code:
1
2
3
4
5
6
public interface IMyCollection : IList, ICollection, IEnumerable, ICloneable
{}

public class MyCollection<T> : List<T>, IMyCollection
    where T : ICloneable
{}


List<T> implementeerd alles uit de IMyCollection al, behalve ICloneable. Wat is nu de manier om de methode Clone() te implementeren, waarbij alle elementen uit de lijst ook gecloned worden?

In eerste instantie dacht ik zoiets:
code:
1
2
3
4
5
6
7
public object Clone()
{
    MyCollection<T> clone = new MyCollection<T>();
    foreach(T item in this)
        clone.Add(item.Clone());
    return clone;
}

Het probleem van bovenstaand is, dat als ik dit definieer
code:
1
2
public MyIetsCollection : MyCollection<Iets>
{}

de Clone(), van een MyIetsCollection -instantie, een object van het type MyCollection<T> teruggeeft. Dit kan niet gecast worden naar een MyIetsCollection .

M'n volgende poging was:
code:
1
2
3
4
5
6
7
8
9
10
public object Clone()
{
    MyCollection<T> clone = this.MemberWiseClone() as IMyCollection;
    for(int i=0; i<this.Count; i++)
    {
        clone[i] = ((T)this[i]).Clone();
    }
    return clone;
    
}

Helaas levert dit geen -soort van- deep copy op. Wanneer ik iets in de originele collection verander, verandert dit ook in de geclonede collection, en andersom.

Ik zit even vast... hoe dit te doen?

[ Voor 5% gewijzigd door Verwijderd op 17-07-2006 17:04 . Reden: paar foutjes er uit ]


  • whoami
  • Registratie: December 2000
  • Nu online
Het probleem met ICloneable is, dat er eigenlijk nergens vastgelegd is of de Clone method nu een deep of een shallow clone moet returnen.

Jouw probleem met je eerste Clone method implementatie, is dat je eigenlijk geen echte Clone maakt. Je maakt wel een nieuwe collectie, maar je add er referenties naar de objecten. Dwz dus dat, als je iets wijzigt aan een object in je geclonede collectie, dat die wijziging ook in je originele collectie zal doorgevoerd worden.
Je zal dus zowiezo clones van de objecten in je clone collectie moeten toevoegen.
Ik heb dit opgelost door mbhv serialization op een makkelijke manier clone's te maken van de objecten die aan mn lijst worden toegevoegd.
Maw, je zal ieder object dat je aan die c ollectie wilt toevoegen moeten clonen.
Dit kan je makkelijk doen als de classes waar die objecten types van zijn, Serializable zijn ([Serializable]).
In de Clone method van die classes, zorg je er dan gewoon voor dat je je object serializeert -naar een MemoryStream-, en dan deserializeert, en het gedeserializeerde object returned. Op die manier heb je een echte clone.
Ik geloof dat ik hier ooit eerder eens een voorbeeldje gepost had...

https://fgheysels.github.io/


Verwijderd

Topicstarter
Wat ik niet helemaal snap,is dat ik voor mijn gevoel met onderstaande code toch ook clones toevoeg (of vervang) in de nieuwe collectie? Ik heb er even een stukje debug bijgezet:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
public object Clone()
{
    MyCollection<T> clone = this.MemberWiseClone() as IMyCollection;
    for(int i=0; i<this.Count; i++)
    {
        Debug.WriteLine(String.Format("before: this[{0}]={1}, clone[{0}]={2}", i, this[i].GetHashCode(), clone[i].GetHashCode()));

        clone[i] = ((T)this[i]).Clone();

        Debug.WriteLine(String.Format("after: this[{0}]={1}, clone[{0}]={2}", i, this[i].GetHashCode(), clone[i].GetHashCode()));
    }
    return clone;    
}


Output:
code:
1
2
before: this[0]=325432, clone[0]=325432
after: this[1]=432431, clone[1]=432431


De objecten in m'n collectie worden dus daadwerkelijk gecloned, helaas wordt de originele collectie ZELF aangepast :?

Maar als ik het goed begrijp is serialiseren/deserialiseren de enige methode? Ik was dit ook al enkele keren tegengekomen, dus ik ben er bang voor. Lijkt me relatief erg traag ;(
Bedankt iig.

  • EfBe
  • Registratie: Januari 2000
  • Niet online
ICloneable moet je niet implementeren. Je moet je eigen interface maken. ICloneable is een interface die MS nooit had willen releasen, echter hij is er nu maar ze adviseren hem te negeren.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 13-02 18:54

.oisyn

Moderator Devschuur®

Demotivational Speaker

Als je clone functionaliteit wilt inbouwen dat ook werkt met afgeleiden van die class, zul je de implementatie van clone moeten opsplitsen in twee delen (hoewel je ook wel met 1 weg zou komen als je gebruik maakt van reflection of de MemberWiseClone method). De ene deel creëert het object van het goede type, het tweede deel zorgt voor de initialisatie van de members. Het idee is dat je de nieuwe instance alleen in de meest afgeleide class maakt, waarna je een functie aanroept die alle members kopiëert (nadat de implementatie van de superclass is aangeroepen)

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class A
{
    private int i;

    public A Clone()
    {
        A a = new A();
        a.CloneMembers(this);
        return a;
    }

    protected void CloneMembers(A a)
    {
        i = a.i;
    }
}

public class B : A
{
    private int j;

    public B Clone()
    {
        B b = new B();
        b.CloneMembers(this);
        return b;
    }

    protected void CloneMembers(B b)
    {
        base.CloneMembers(b);
        j = b.j;
    }
}


De Clone zou je dmv reflection alleen in de base class hoeven te implementeren (nadeel: al je cloneable classes moeten van een bepaalde base overerfen), de CloneMembers code zou in principe ook in een (protected) constructor kunnen (is wellicht mooier)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • party42
  • Registratie: Oktober 2000
  • Laatst online: 13-02 15:56
Onderstaand is een methode die een object (afgeleid van ICloneable) deep-cloned. Het werkt naar behoren...

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
        /// <summary>
        /// Clone het object en als zijn properties.
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            //First we create an instance of this specific type.
            object newObject = Activator.CreateInstance(this.GetType());

            //We get the array of fields for the new type instance.
            PropertyInfo[] fields = newObject.GetType().GetProperties();

            //Teller om Fields van gekloonde object mee telaten lopen
            int i = 0;
            //Loop door alle veld properties.
            foreach (PropertyInfo fi in this.GetType().GetProperties())
            {
                //We query if the fields support the ICloneable interface.
                Type ICloneType = fi.PropertyType.GetInterface("ICloneable", true);
                if (ICloneType != null)
                {
                    //Getting the ICloneable interface from the object.
                    ICloneable IClone = (ICloneable)fi.GetValue(this, null);

                    //We use the clone method to set the new value to the field.
                    if (IClone != null)
                        fields[i].SetValue(newObject, IClone.Clone(), null);
                }
                else
                {
                    //Controleer of het type van IList is.
                    //GetInterface() werkt niet.
                    if (fi.GetValue(this, null) is IList)
                    {
                        //Haal de sourcelist op.
                        IList sourceList = (IList)fi.GetValue(this, null);
                        if (sourceList != null)
                        {
                            //Maak vervolgens een Destination list aan
                            IList destList = (IList)Activator.CreateInstance(sourceList.GetType());
                            //Assign de destList aan het gekloonde object.
                            fields[i].SetValue(newObject, destList, null);

                            //Loop door alle items in de lijst en kloon deze.
                            foreach (object obj in sourceList)
                            {
                                //Ook hier moet ieder item van ICloneable zijn.
                                //Ieder item wordt weer gekloond (recursief)
                                ICloneType = obj.GetType().GetInterface("ICloneable", true);
                                if (ICloneType != null)
                                {
                                    //Voeg alle gekloonde list items toe aan de lijst in het gekloonde object.
                                    ICloneable clone = (ICloneable)obj;
                                    destList.Add(clone.Clone());
                                }
                            }
                        }
                    }
                    else
                    {
                        //Wanneer het om Value type kan SetValue meteen gebruikt worden..
                        object objValue = fi.GetValue(this, null);
                        if (objValue != null)
                            fields[i].SetValue(newObject, objValue, null);
                    }
                }                
                i++;
            }
            return newObject;
        }

Everyday's an endless stream, of cigarettes and magazines...


  • whoami
  • Registratie: December 2000
  • Nu online
party42 schreef op donderdag 20 juli 2006 @ 10:07:
Onderstaand is een methode die een object (afgeleid van ICloneable) deep-cloned. Het werkt naar behoren...

C:
1
snip
Dit zal zeker wel goed werken, maar het nadeel van deze oplossing is: traagheid (reflection), en die method is ook niet in één oogopslag duidelijk.
Dan zou ik toch opteren voor de serializatie (ok, ook niet echt de snelste oplossing), maar toch behoorlijk compacter en wellicht toch nog sneller; of de manier die .oisyn voorstelt.
Al heb je bij .oisyn's oplossing natuurlijk wel het nadeel van onderhoudbaarheid: als je iets wijzigt in je class, mag je ook niet vergeten om die Clone() method aan te passen.

https://fgheysels.github.io/


  • party42
  • Registratie: Oktober 2000
  • Laatst online: 13-02 15:56
Nadeel van serialisatie is (en daar liepen wij tegenaan) dat onze User classes IPrincipals in zich hadden. IPrincipal is niet te serializeren.

Dat de method in 1 oogopslag niet duidelijk is vind ik nogal subjectief... :)

Everyday's an endless stream, of cigarettes and magazines...

Pagina: 1