[C#] ReferenceEquals

Pagina: 1
Acties:

  • Tyf
  • Registratie: December 2002
  • Laatst online: 28-11 20:18
Is er een manier om twee objects te vergelijken of deze dezelfde referentie hebben?
Ik zit met een programma waarvan ik de structuur van een object wil bijhouden. Hiervoor overloop ik alle members van dit object. het probleem is dat men soms members kan hebben welke terug verwijzen naar het parent object. Wanneer hier geen rekening mee gehouden wordt krijgen we dus een oneindige loop.

Ik dacht, ik hou een lijst van alle objecten bij welke ik al doorlopen heb. Elke keer ik een nieuw object wil overlopen kijk ik in mijn lijst of deze al bestaat aan de hand van zijn reference. ReferenceEquals leek hier een goede manier voor maar ik vond volgende uitleg terug:

"The difficulty comes from the fact that ReferenceEquals expects two System.Objects as parameters. This means that our value types will get boxed onto the heap as they are passed in to this routine. Normally, because of the way the boxing process works, they will get boxed separately to different memory addresses on the heap. This of course means the call to ReferenceEquals returns false."

Dit ondervond ik toen ik volgende probeerde:
C#:
1
2
3
4
5
6
7
8
9
10
11
        public bool containsObject(ArrayList list, Object obj)
        {
            bool found = false;
            
            for (int i = 0; i < list.Count && found == false; i++)
            {
                found = Object.ReferenceEquals(obj, list[i]);
            }

            return found;
        }

Hoewel ik zeker was dat er objecten dubbel in de arraylist zaten, bleef ik maar false als result terug te krijgen. Ook al voegde ik een object toe waarvan ik 100% was dat deze er al zat. (direct te merken aan de oneindige loop bij mijn algoritme)

Is er een andere manier om te vergelijken of twee objecten hetzelfde zijn? Ik had al gelezen dat men hiervoor soms een hashtable gebruikt:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Hashtable stackMap = new Hashtable( null, new ReferenceComparer() ); 

....

stackMap.Add( oo, "" );
stackMap.Contains( oo ) //true hier
....

private class ReferenceComparer : IComparer
        {
            #region IComparer Members

            public int Compare( object x, object y )
            {
                return object.ReferenceEquals( x, y ) ? 0 : -1;
            }

            #endregion
        }


In bovenstaande manier gaat volgens ik gelezen heb de ReferenceComparer gebruikt worden wanneer men de Contains methode oproept. Twee problemen, blijkbaar wordt deze toch niet opgeroepen en tweede probleem: krijgen we weer niet te maken met het boxing probleem eerder uitgelegd.

Even nog voor de duidelijkheid, ik wil niet weten of een object dezelfde waarde heeft. Ik wil weten of deze dezelfde reference heeft als een eerder toegevoegd object. Wanneer dit zo is, weet ik dat ik er niet verder hoef naar te kijken.

[ Voor 13% gewijzigd door Tyf op 16-05-2007 00:50 ]


  • DrDelete
  • Registratie: Oktober 2000
  • Laatst online: 20:46
Check item 9 van Effective C# (Bill Wagner), daarin vermeld hij het volgende:

Verander nooit het gedrag van ReferenceEquals, het doet precies wat het moet doen: deze retourneert TRUE als 2 variabelen refereren naar hetzelfde object, d.w.z. dezelfde identiteit. Of je nou reference of value types vergelijkt, deze methode zal altijd de identiteit testen maar niet de inhoud. Dit betekent dat altijd FALSE geretourneerd wordt voor value types ook al test je de valuetype op zichzelf, dit is vanwege boxing.

Ik ga er van uit dat jouw types die je in de ArrayList stopt een value type is (een struct) ?

Dan krijg je dus problemen met het vergelijken.

Mijn tip: probeer met hashtable te werken en override equals en gethashcode, zie de MSDN help hierover, je moet rekening houden met de 3 eigenschappen van equality: reflexive, symmetric en transitive. Als je daar aan voldoet is elk object terug te vinden in de hashtable.

  • bigbeng
  • Registratie: Augustus 2000
  • Laatst online: 26-11-2021
Jouw algoritme doet het op dit moment alleen als het laatste object in je ArrayList dezelfde referentie heeft.
Zodra je een resultaat hebt moet je gelijk uit je loop springen:
[code=c#]
public bool containsObject(ArrayList list, Object obj)
{
bool found = false;

for (int i = 0; i < list.Count && found == false; i++)
{
found = Object.ReferenceEquals(obj, list[i]);
if ( found )
break;
}

return found;
}
[/code]

edit:

tis te vroeg... :)

[ Voor 3% gewijzigd door bigbeng op 16-05-2007 08:31 ]


  • EfBe
  • Registratie: Januari 2000
  • Niet online
Wat zit er in die arraylist, valuetypes? Dan werkt referenceequals niet, want value type instances zijn altijd uniek, immers, het zijn values, tenzij je de reference doorgeeft aan een method, maar daar praten we hier niet over.

m.a.w.: zitten er value types in: gebruik Equals, zitten er objects in, gebruik ReferenceEquals of cast beide naar object en gebruik '=='.

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


  • DrDelete
  • Registratie: Oktober 2000
  • Laatst online: 20:46
EfBe schreef op woensdag 16 mei 2007 @ 09:14:
Wat zit er in die arraylist, valuetypes? Dan werkt referenceequals niet, want value type instances zijn altijd uniek, immers, het zijn values, tenzij je de reference doorgeeft aan een method, maar daar praten we hier niet over.

m.a.w.: zitten er value types in: gebruik Equals, zitten er objects in, gebruik ReferenceEquals of cast beide naar object en gebruik '=='.
Als je value types gebruikt zou ik om performance redenen de == operator opnieuw definieren (default werkt het met reflectie en dit is niet aan te raden vanwege de traagheid).

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Hoe kom je daar nou bij. De '==' op value types wordt gewoon de ceq instructie in IL, niks reflection, tenzij de operator helemaal niet is gedefinieerd (bv bij een normaal struct). Is '==' niet gedefinieerd (bv een normaal struct) dan compileert je code niet, je moet dan al Equals aanroepen (wat een reference compare doet standaard in Object), of je moet de operator zelf implementeren en als de implementator dan reflectie gebruikt, tja, dan is het traag, maar dat is dus niet per definitie zo.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
    public struct A
    {
        public int Foo;
    }

//....

            A a = new A();
            a.Foo = 10;

            A b = new A();
            b.Foo = 20;
            bool result = (a == b);  // won't compile

[ Voor 5% gewijzigd door EfBe op 16-05-2007 12:47 ]

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


  • DrDelete
  • Registratie: Oktober 2000
  • Laatst online: 20:46
EfBe schreef op woensdag 16 mei 2007 @ 12:47:
Hoe kom je daar nou bij. De '==' op value types wordt gewoon de ceq instructie in IL, niks reflection, tenzij de operator helemaal niet is gedefinieerd (bv bij een normaal struct). Is '==' niet gedefinieerd (bv een normaal struct) dan compileert je code niet, je moet dan al Equals aanroepen (wat een reference compare doet standaard in Object), of je moet de operator zelf implementeren en als de implementator dan reflectie gebruikt, tja, dan is het traag, maar dat is dus niet per definitie zo.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
    public struct A
    {
        public int Foo;
    }

//....

            A a = new A();
            a.Foo = 10;

            A b = new A();
            b.Foo = 20;
            bool result = (a == b);  // won't compile
De eis van de == operator opnieuw definieren geldt alleen als je begint aan:
1) equals override
en daardoor verplicht
2) gethashcode override
dan ook
3) operator == opnieuw definieren

punt 3 alleen voor value types

  • Tyf
  • Registratie: December 2002
  • Laatst online: 28-11 20:18
Alvast bedankt voor de reacties. Ik ga deze morgen verder uitpluizen.
Lange dag gehad en lange nacht ervoor ben moe :)

Ik heb ook nog verder geprobeerd op de arraylist-manier. Dit werkt nu (morge ook code geven), maar had liever met de hashtable gewerkt, omdat de arraylist-manier wat gepruts lijkt.

Update:
De arraylist wordt opgevuld door een object te overlopen (al zijn velden en eigenschappen). Wanneer een veld dus een value type is, zit er een value type op de arraylist. Als het een reference type is zal deze op de arraylist zitten. Wanneer ik dit bijvoorbeeld met een form doe, zal een member van dit veld bijvoorbeeld tabcontrol een eigenschap partent hebben. Bedoeling is dat er op dat moment geweten is dat partent verwijs naar een object welke al doorlopen is.

Dit is wat ik momenteel doe (heb index later nodig in mijn app).
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        public int containsObject(ArrayList list, Object obj)
        {
            int index = -1;
            for (int i = 0; i < list.Count && index == -1; i++)
            {

                if (list[i] == obj)
                    index = i;
                else
                {
                   
                        if (list[i].Equals(obj))
                            index = i;
                }
            }

            return index;
        }


Dit lijkt te werken, hoewel ik denk dat het niet correct is. waarom referenceEquals niet werkt, geen idee. Een form is toch een referenceType. Een Form zit in de arraylist. Er wordt nu gecontroleerd met een object, welke weer de form is, of deze al voorkomt in de arraylist. Dit met behulp van referenceEquals. Het resultaat is elke keer false?? Snap het niet goed meer. Ik wil echt weten of een object verwijs naar een eerder gedefinieerd object.

Ik zou zelf een lijst kunnen implementeren op basis van de reference van een object (dus ook reference van een valuetype) maar dit lijkt me het wiel opnieuw uitvinden.

Ik zal nu nog eens terug kijken naar de hashtable en het overriden van equal en gethashcode. (update, lees veel info over waarom equal en gethashcode overschreven moet worden, maar hoe je dit zou kunnen doen op basis van de reference niet ;( )

[ Voor 105% gewijzigd door Tyf op 17-05-2007 10:46 ]


  • EfBe
  • Registratie: Januari 2000
  • Niet online
DrDelete schreef op woensdag 16 mei 2007 @ 12:56:
[...]


De eis van de == operator opnieuw definieren geldt alleen als je begint aan:
1) equals override
en daardoor verplicht
2) gethashcode override
dan ook
3) operator == opnieuw definieren

punt 3 alleen voor value types
De operator moet je sowieso definieren, want value types (== structs) hebben die operator standaard niet, zie mijn code. Dus ik snap je probleem niet helemaal.

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


  • Tyf
  • Registratie: December 2002
  • Laatst online: 28-11 20:18
EfBe, ik wil de reference van objecten vergelijken. Welke soort object dit ook is (ref of value type).
De gethashcode overschrijven wil ik wel doen, maar wat moet ik de plaats schrijven? Ik zou dit dan op basis van de reference willen doen en niet de value. Maar hoe krijg ik de reference vast van een value type? Ik weet op voorhand niet welke objecten ik terug krijg en of dit nu ref of value types zijn.

Klopt het ook als ik denk dat wanneer ik een value type in de arraylist steek, ik een nieuwe referentie creëer? Dit door het boxen van dit object. Toch wil ik weten of dit object al voorkomt in de arraylist op basis van zijn reference id.

Misschien ook even vermelden dat alles op het compact framework moet werken. Geen idee of dit voor sommige zaken waar we het hier over hebben uitmaken (vermoed van niet).

[ Voor 32% gewijzigd door Tyf op 17-05-2007 14:32 ]


  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Tyf schreef op donderdag 17 mei 2007 @ 14:00:
Maar hoe krijg ik de reference vast van een value type?
Simpel antwoord: dat kan niet.
Klopt het ook als ik denk dat wanneer ik een value type in de arraylist steek, ik een nieuwe referentie creëer? Dit door het boxen van dit object.
Klopt. Een arraylist bestaat alleen uit verwijzingen naar objecten. Om een value in zo'n arraylist te proppen moet je de waarde inpakken in een object (dit heet boxing). Daarbij wordt een nieuw object gemaakt en deze heeft dus een andere verwijzing dan elk ander object binnen je applicatie.
Toch wil ik weten of dit object al voorkomt in de arraylist op basis van zijn reference id.
Dat kan dus alleen met waarden van reference-types, niet met waarden van value-types.

Maar ik zie het probleem niet zo erg; je kan toch een functie schrijven wat controleert of een waarde al in de arraylist voorkomt door voor je reference-typen naar de referenties te kijken en voor je value-typen naar de values zelf? Op de plaats waar je de objecten aanlevert weet je wel wat het type van het object is...

Zie eventueel het comparator-pattern.

[ Voor 8% gewijzigd door Infinitive op 17-05-2007 23:43 ]

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


  • Zr40
  • Registratie: Juli 2000
  • Niet online

Zr40

Moderator General Chat

heeft native IPv6

Tyf schreef op donderdag 17 mei 2007 @ 14:00:
EfBe, ik wil de reference van objecten vergelijken. Welke soort object dit ook is (ref of value type).
Een value type is per definitie geen object. Je kan er wel een object omheen maken (boxing), maar als je die gaat vergelijken op reference vergelijk je het doosje in plaats van de inhoud.

Wat is een value type: Alle basistypen (alle getaltypen, char, bool), DateTime, TimeSpan, enums en alle structs.

[ Voor 15% gewijzigd door Zr40 op 18-05-2007 00:38 ]


  • Tyf
  • Registratie: December 2002
  • Laatst online: 28-11 20:18
Welja ben ondertussen dus ook tot de conclusie gekomen dat wat ik wou niet mogelijk is.
Waarom ik dit wou doen was, omdat ik objecten overloop en soms verwijst een bepaalde property of member naar een reeds overlopen object/value. Dan wou ik in de plaats opnieuw deze waarde bij te houden, verwijzen naar het reeds opgeslagen object. Nu het is geen ramp nu dit niet gebeurd. Het belangrijkste is dat het bij objecten wel werkt. Daar kwam ik vaak in een loop terecht wanneer er cirulaire of dubbele referenties waren naar reeds onderzochte objecten.

Bedankt voor de tips en richtlijnen!

[ Voor 3% gewijzigd door Tyf op 18-05-2007 21:09 ]

Pagina: 1