The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
.Gertjan. schreef op dinsdag 25 februari 2014 @ 14:44:
Zucht... Heb nu weer iets leuks aan mijn fiets hangen
C#:
1 2 3 List<T> data = GetData(); T dataPart1 = data[0]; T dataPart2 = data[0];
Geeft mij
dataPart1 == dataPart2 => False
dataPart1 == data[0] => True
dataPart2 == data[0] => True
Ze lijken naar hetzelfde te wijzen:
&dataPart1
0x031b1414
$x1: 0x02cdab88
&dataPart2
0x031b1418
$x2: 0x02cdab88
Whut.... Zal wel weer ergens iets verkloot hebben...
Edit:
Nog spannender
dataPart1 == dataPart1 => False
dataPart2 == dataPart2 => False
(tmp1 as T) == (tmp2 as T) => True
Dus pas als je expliciet naar een T cast gaat de vergelijking goed... Spannend
1
| ReferenceEquals(dataPart1, dataPart2); |
Wat is de output van dat?
.oisyn: Échte programmeurs haten PHP met een passie. Ben jij soms geen echte programmeur?
Meh, heb al een oplossing. Was meer een [rant] [/]BtM909 schreef op dinsdag 25 februari 2014 @ 15:01:
[...]
Zal ik 't afsplitsen naar een apart topic?
Een nieuw topic is leuk, maar dat wordt er dan zo een waar de topicstarter niets meer laat weten
Die is dan wel weer true... Nou ja, heb er inmiddels omheen gewerkt, waarschijnlijk iets wat quircky is met de generics (het is een class die redelijk diep in de generics zit)...Korben schreef op dinsdag 25 februari 2014 @ 15:00:
[...]
C#:
1 ReferenceEquals(dataPart1, dataPart2);
Wat is de output van dat?
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
Verwijderd
Je kan wel laten weten hoe je het hebt opgelost.Gertjan. schreef op dinsdag 25 februari 2014 @ 15:04:
[...]
Meh, heb al een oplossing. Was meer een [rant] [/]
Een nieuw topic is leuk, maar dat wordt er dan zo een waar de topicstarter niets meer laat weten
[...]
Dan zou je ook wat meer over de context moeten weten (wat ik plaatste was meer een debug setje wat ik uitvoerde). Ik wilde na verwerken een bepaald item uit de lijst wippen, maar hij kon hem niet meer terugvinden (hij liet ze dus staan). De data.Remove(dataPart1) deed niets omdat hij dataPart1 niet kon terugvinden in de lijst (terwijl hij dus wel in de lijst staat, als je handmatig zocht en de explicite cast deed kreeg je wel een match).Verwijderd schreef op dinsdag 25 februari 2014 @ 15:05:
[...]
Je kan wel laten weten hoe je het hebt opgelost
Mijn oplossing is nu dat ik bij het zoeken van een item in de lijst de index vasthoudt, in plaats van een FirstOrDefault zoek ik nu de IndexOf. Die gebruik ik later dan weer om het item uit de lijst te wippen.
De betreffende code was bedoeld voor een import waar uit diverse lijsten data werd verwerkt, na verwerken moest het uit de "werklijst" gehaald worden... Maar ik kreeg dubbele imports omdat de Remove niets meer kon terugvinden en dus de lijst niet leeghaalde...
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
Ik verwacht iets met een conversion operator, of een explicit interface implementation
[ Voor 47% gewijzigd door Woy op 25-02-2014 15:34 ]
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
U vraagt, wij draaienWoy schreef op dinsdag 25 februari 2014 @ 15:33:
.Gertjan.: Nu wil ik ook wel een test-case zien
Ik verwacht iets met een conversion operator, of een explicit interface implementation
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
| private List<T> UpdateElementsImportedWithDataAlreadyInSystem<T> ( List<T> elementsOfferedToImporter, List<T> elementsAlreadyInTheSystem, ) where T : class, IImportableEntity { List<T> updatedElements = new List<T>(); elementsAlreadyInTheSystem.ForEach(systemElement => { T elementMatchedFromImporter = FindItemMatchInCollection(systemElement, elementsOfferedToImporter); if (elementMatchedFromImporter == null) return; //No match so no work to perform //[...] //Remove the processed element from the list and add it to the list of elements to update elementsOfferedToImporter.Remove(elementMatchedFromImporter); updatedElements.Add(elementMatchedFromImporter); }); return updatedElements; } private T FindItemMatchInCollection<T>(T element, List<T> collection) where T : class, IImportableEntity { //[...] Pre checks //Find the first match based on the system and id return collection.FirstOrDefault(item => element.ImportID.Equals(item.ImportID, StringComparison.OrdinalIgnoreCase) && element.ImportSystemID.Equals(item.ImportSystemID, StringComparison.OrdinalIgnoreCase)); } |
In regel 17 kan hij op de elementMatchedFromImporter niet meer terugvinden in de originele collection. Dit terwijl elementMatchedFromImporter wel gelijk is als je hem rechtstreeks vergelijk met een element in de lijst (dus via elementMatchedFromImporter == elementsOfferedToImporter[0], dit levert wel een true op).
Tijdens het debuggen kwam ik er ook achter dat 2 vars die wijzen naar hetzelfde element in de List niet met elkaar te vergelijken zijn (zie de eerdere post). Ze zijn wel gelijk bij ReferenceEquals maar .Equals en == vertikken het. Bij expliciet casten naar T gaat het wel weer goed. Zelfs de var met zichzelf vergelijken (elementMatchedFromImporter == elementMatchedFromImporter) gaf een false
Ik moest op een wat lullige manier om dit probleem heenwerken aangezien de Remove het element dus niet kon vinden (ook niet met een explicite cast). Waarschijnlijk gaat ergens iets borked. Nu ook maar de index opgevraagd in de FinditemMatchInCollection functie zodat ik daarmee het element kan weggooien.
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
Ik zal vanavond thuis eens kijken of ik het kan reproduceren, ben wel benieuwd.
“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”
Woy schreef op dinsdag 25 februari 2014 @ 16:02:
Op regel 17 geeft elementsOfferedToImporter.Any( x=> x == elementMatchedFromImporter) dus altijd false terug?
Ik zal vanavond thuis eens kijken of ik het kan reproduceren, ben wel benieuwd.
1
2
| bool found1 = elementsOfferedToImporter.Any(x => x == elementMatchedFromImporter); bool found2 = elementsOfferedToImporter.Contains(elementMatchedFromImporter); |
found1 => true
found2 => false
Ik werkte in eerste instantie zonder Linq, ik deed dus een .Remove, daar vond hij niets terug (als in, er werd niet verwijderd). Maar het lijkt erop dat een Linq statement wat logischer gedrag vertoont.
Overigens werkte elementMatchedFromImporter == elementsOfferedToImporter[0] ook (zie eerste post), mogelijk dat het Linq statement daarom ook werkt.
Edit:
Ben wel benieuwd naar je bevindingen. Ben zelf ook wel getriggerd door dit "probleem". Misschien dat ik dit ook eens door een decompiler heentrek om te kijken of daar iets zinnigs te zien is.
[ Voor 10% gewijzigd door .Gertjan. op 25-02-2014 16:09 ]
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
Overigens, heb je ook lijsten waar geen elementen in gaan? Vanwaar deze lange namen?
Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten
Hoezo? Misschien dat je kunt wijzen op het deel dat ik volgens jou over het hoofd heb gezien.pedorus schreef op dinsdag 25 februari 2014 @ 16:17:
Ik denk dat er iemand deze nog eens door moet gaan lezen: MSDN: Guidelines for Overriding Equals() and Operator == (C# Programming Guide)
Overigens, heb je ook lijsten waar geen elementen in gaan? Vanwaar deze lange namen?
Er wordt niets overriden. Het frapante is daarnaast dat het gedrag anders loopt als er expliciet wordt gecast of als er gewerkt wordt met een Linq statement.
Ik denk dus niet zozeer dat ik mij opnieuw moet verdiepen in hoe de taal werkt
Qua naming hanteer ik logische namen boven korte namen. Aangezien er in deze class en functie meerdere lijsten worden gebruikt (importlijst, systeemlijst, nieuwe items, geupdate items, te verwijderen items, etc) houd ik graag de namen duidelijk
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
Tussen-de-soep-en-de-aardappelen-door-haastige-edit:
http://blog.coverity.com/2014/01/13/inconsistent-equality/ maybe?
Mocht ik er helemaal langs zitten: my bad
[ Voor 22% gewijzigd door RobIII op 25-02-2014 17:20 ]
There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.
Je eigen tweaker.me redirect
Over mij
Als die objecten niets overriden, dan lijkt me dit een hele rare situatie. Want dan zou je steeds bij de equals/== van object uit moeten komen..Gertjan. schreef op dinsdag 25 februari 2014 @ 16:22:
Er wordt niets overriden. Het frapante is daarnaast dat het gedrag anders loopt als er expliciet wordt gecast of als er gewerkt wordt met een Linq statement.
Echter, die link die ik gaf is een mooi voorbeeld van hoe je zo'n situatie kan krijgen, kijk maar in de comments. Is trouwens later gefixt, maar helaas weer fout: MSDN: Implementing the Equals Method
Ik zeg ook niet dat jijzelf degene zou zijn die dit heeft veroorzaakt.
Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten
pedorus schreef op dinsdag 25 februari 2014 @ 17:58:
[...]
Als die objecten niets overriden, dan lijkt me dit een hele rare situatie. Want dan zou je steeds bij de equals/== van object uit moeten komen.
Echter, die link die ik gaf is een mooi voorbeeld van hoe je zo'n situatie kan krijgen, kijk maar in de comments. Is trouwens later gefixt, maar helaas weer fout: MSDN: Implementing the Equals Method
Ik zeg ook niet dat jijzelf degene zou zijn die dit heeft veroorzaakt.
Oei, het lijkt dat er inerdaad toch een onderliggende class is die de equals overschrijft. Ding is er door een collega tussengeprikt.Feanathiel schreef op dinsdag 25 februari 2014 @ 22:05:
Wat is de implementatie van de klasse die je als T meegeeft (en eventueel de inherited class(es))? Ik heb 'm lokaal letterlijk overgenomen, maar krijg 'm ook niet gerepro'd.
Desalniettemin is het gedrag anders als er expliciet gecast wordt, dat verbaast mij het meest. De objecten lijken naar hetzelfde te wijzen (getuige de ReferenceEquals), maar een directe == / Equals faalt waar een ==/Equals na een expliciete cast naar T van beide objecten een ander resultaat geeft (terwijl ze al T waren).
Kan nu even niet bij de code, maar zal morgen wat details erbij gooien en kijken of ik het probleem kan isoleren. De equals die mogelijk wordt aangeroepen kijkt overigens of de objecten dezelfde pointer hebben, zo niet vergelijkt hij op data niveau, dus het gedrag is onverwachts (iig voor mij
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
Het zou fijn zijn als je 't een beetje kunt beperken tot iets zoals hier getoond wordt inderdaad want ik zie door de bomen 't bos niet meer.Gertjan. schreef op dinsdag 25 februari 2014 @ 23:34:
Kan nu even niet bij de code, maar zal morgen wat details erbij gooien en kijken of ik het probleem kan isoleren.
There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.
Je eigen tweaker.me redirect
Over mij
Ga ik doen, ik wil het even isoleren naar een unittest zodat ik het gedrag ook geautomatiseerd kan testen. Wordt vervolgdRobIII schreef op dinsdag 25 februari 2014 @ 23:46:
[...]
Het zou fijn zijn als je 't een beetje kunt beperken tot iets zoals hier getoond wordt inderdaad want ik zie door de bomen 't bos niet meer
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
Verwijderd
Zo uit mijn hoofd gebruikt een Contains() bijvoorbeeld een andere dan de Any(), maar dat weet ik niet zeker.
Wel interessante case dit
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten
Functioneel zijn references pointers en het heeft wel degelijk daarmee te maken, anders zou er geen verschil hoeven worden gemaakt tussen Equals en ReferenceEquals.pedorus schreef op woensdag 26 februari 2014 @ 10:49:
Dit heeft niet zoveel te maken met pointers (!=references), maar meer met inheritance en/of operator overloading.
Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.
Nou ja, het feit dat de pointers gelijk zijn (en ReferenceEquals dus true) maakt het (iig voor mij) verwarrend.farlane schreef op woensdag 26 februari 2014 @ 10:13:
Meestal moet ik een klein beetje gniffelen om dit soort dingen: Ondanks dat C# een "high level" taal is die o-zo-makkelijk is blijkt eigenlijk dat je gewoon moet weten wat een pointer is en alle andere moeilijke dingen van lower level talen. ( Die je eigenlijk toch al moest weten )
Ik probeer nu de case te isoleren en daar krijg ik de fout nog niet. Mogelijk dat er dus ergens anders iets met de objecten/classes gebeurt waar ik overheen kijk. Als ik de case nabouw in een unittest dan werken alle compares zoals verwacht, toch nog maar even verder zoeken.
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
Nadere inspectie levert mij op dat om de een of andere manier de ==/Equals in sommige gevallen niet gebruik maakten van de basis class. Hierdoor verschilde de resultaten tussen de verschillende vergelijkingen.
De te testen class
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
| public class TestClass : TmpBaseEntity { public string Value { get; set; } } public class TmpBaseEntity { public override bool Equals(object obj) { Trace.WriteLine("Hit equals"); return base.Equals(obj); } public static bool operator ==(TmpBaseEntity a, TmpBaseEntity b) { Trace.WriteLine("Hit =="); if (a == (object)null && b == (object)null) { return true; } if (a == (object)null || b == (object)null) { return false; } return a.Equals(b); } public static bool operator !=(TmpBaseEntity a, TmpBaseEntity b) { Trace.WriteLine("Hit !="); if ((object)a == null && (object)b == null) { return false; } if ((object)a == null || (object)b == null) { return true; } return !a.Equals(b); } } |
De test
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
| [TestMethod] [TestCategory(BaseEntityEqualityTestsCategory)] public void EqualityWorksAsExpectedOnGenericTypes() { EnhancedList<TestClass> tc = new EnhancedList<TestClass>() { new TestClass() { Value = "v1" }, new TestClass() { Value = "v2" }, new TestClass() { Value = "v3" } }; GetElementFromListAndComare(tc); } private void GetElementFromListAndComare<T>(EnhancedList<T> list) where T : class { T var1 = GetFirst(list); T var2 = GetFirst(list); List<CompareRes> compares = new List<CompareRes>(); compares.Add(new CompareRes("var1 == var2", () => var1 == var2)); compares.Add(new CompareRes("var1 == var1", () => var1 == var1)); compares.Add(new CompareRes(" var1 == list[0]", () => var1 == list[0])); compares.Add(new CompareRes("var2 == list[0]", () => var2 == list[0])); compares.Add(new CompareRes("var1.Equals(var2)", () => var1.Equals(var2))); compares.Add(new CompareRes("var1.Equals(var1)", () => var1.Equals(var1))); compares.Add(new CompareRes("var2.Equals(var1)", () => var2.Equals(var1))); compares.Add(new CompareRes("var1.Equals(list[0])", () => var1.Equals(list[0]))); compares.Add(new CompareRes("list[0].Equals(var1)", () => list[0].Equals(var1))); compares.Add(new CompareRes("list.Contains(var1)", () => list.Contains(var1))); compares.Add(new CompareRes("ReferenceEquals(var1, var2)", () => ReferenceEquals(var1, var2))); compares.Add(new CompareRes("ReferenceEquals(var1, list[0])", () => ReferenceEquals(var1, list[0]))); List<CompareRes> errors = compares.Where(C => !C.Result).ToList(); Assert.AreEqual(0, errors.Count, "Failed" + string.Join("\r\n", errors.Select(E => E.Action))); } class CompareRes { public CompareRes(string action, Func<bool> result) { Trace.WriteLine("Running: " + action); Action = action; Result = result(); Trace.WriteLine("Succes: " + Result); } public string Action { get; set; } public bool Result { get; set; } } private T GetFirst<T>(List<T> list) where T : class { return list.First(); } } |
Dit levert de volgende "resultaten" op:
Running: var1 == var2
Succes: True
Running: var1 == var1
Succes: True
Running: var1 == list[0]
Succes: True
Running: var2 == list[0]
Succes: True
Running: var1.Equals(var2)
Hit equals
Succes: True
Running: var1.Equals(var1)
Hit equals
Succes: True
Running: var2.Equals(var1)
Hit equals
Succes: True
Running: var1.Equals(list[0])
Hit equals
Succes: True
Running: list[0].Equals(var1)
Hit equals
Succes: True
Running: list.Contains(var1)
Hit equals
Succes: True
Running: ReferenceEquals(var1, var2)
Succes: True
Running: ReferenceEquals(var1, list[0])
Succes: True
De bug in de Equals zelf is hier niet van toepassing omdat ik gewoon de base aanroep en dit enkel is om te illustreren wat er gebeurt
Frapante is dat als je een breakpoint zet in de test en vervolgens via het Immediate window het volgende uitvoert de .Equals en == wel geraakt worden:
T x = GetFirst(list);
T x2 = GetFirst(list);
x2 == x
Hit ==
Hit equals
true
(Dit leverde voorheen dus een false op omdat de .Equals uit de bocht vloog)
Het lijkt er dus op dat binnen de Generic functie de "verkeerde" == wordt aangeroepen. Dit zorgde voor het vreemde gedrag. Snap nog steeds niet helemaal waarom hij de andere afslag neemt, maar heb iig de bug in mijn Equals kunnen tackelen door nog eens goed naar de code te kijken.
Dat de immediate window de == ook weer anders behandelt zorgde voor nog resultaten die nog verwarrender waren... Een deel van mijn gerapporteerde bevindingen was namelijk op basis van de immediate window tests.
[edit]
Het lijkt er trouwens op dat het aanpassen van de where T : class naar where T : TmpBaseEntity wel de goede functie aanroept...
The #1 programmer excuse for legitimately slacking off: "My code's compiling"
Firesphere: Sommige mensen verdienen gewoon een High Five. In the Face. With a chair.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // T : class
//000040: compares.Add(new CompareRes("var1 == var2", () => var1 == var2));
IL_0000: ldarg.0
IL_0001: ldfld !0 class UnitTestProject1.UnitTest1/'<>c__DisplayClass12`1'<!T>::var1
IL_0006: box !T
IL_000b: ldarg.0
IL_000c: ldfld !0 class UnitTestProject1.UnitTest1/'<>c__DisplayClass12`1'<!T>::var2
IL_0011: box !T
IL_0016: ceq
// T : TmpBaseEntity
//000040: compares.Add(new CompareRes("var1 == var2", () => var1 == var2));
IL_0000: ldarg.0
IL_0001: ldfld !0 class UnitTestProject1.UnitTest1/'<>c__DisplayClass12`1'<!T>::var1
IL_0006: box !T
IL_000b: ldarg.0
IL_000c: ldfld !0 class UnitTestProject1.UnitTest1/'<>c__DisplayClass12`1'<!T>::var2
IL_0011: box !T
IL_0016: call bool [ConsoleApplication89]ConsoleApplication89.TmpBaseEntity::op_Equality(
class [ConsoleApplication89]ConsoleApplication89.TmpBaseEntity,
class [ConsoleApplication89]ConsoleApplication89.TmpBaseEntity) |
Maar de hamvraag is, waarom gebeurd dit dan? Dus dan worden T types zonder definitie altijd vergeleken op basis van operator ==(object, object).
Nog even een minimal case:
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
| class Foo { public static bool operator ==(Foo first, Foo second) { Trace.WriteLine("Override =="); return ((object)first) == ((object)second); } public static bool operator !=(Foo first, Foo second) { Trace.WriteLine("Override !="); return ((object)first) != ((object)second); } } class Program { static void Main(string[] args) { var item = new Foo(); bool a = CompareFoo(item); // Traced bool b = CompareClass(item); // Niet-traced } private static bool CompareFoo<T>(T item) where T : Foo { return item == item; } private static bool CompareClass<T>(T item) where T : class { return item == item; } } |
@hieronder: Thanks, informatief
[ Voor 35% gewijzigd door Feanathiel op 27-02-2014 22:08 ]
Overload resolution happens compile-time. So when we have == between generic types T and T, the best overload is found, given what constraints are carried by T (there's a special rule that it will never box a value-type for this (which would give a meaningless result), hence there must be some constraint guaranteeing it's a reference type). In your Follow Up 2, if you come in with DerivedTest objects, and DerivedTest derives from Test but introduces a new overload of ==, you will have the "problem" again. Which overload is called, is "burned" into the IL at compile-time. – Jeppe Stig Nielsen Jan 5 '13 at 18:51
Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten