maandag, mei 22, 2006

C#, casten met een scherp randje

Persoonlijk houd ik er wel van om op de cutting edge van technologie te zitten. "Where IT bleeds". Echter toen mijn collega een bepaalde cast niet voor elkaar kreeg schrok ik toch wel even.
Dat de volgende cast niet werkt, kan ik begrijpen. (Sirecore is het webapplicatieplatform wat wij gebruiken)

Sitecore.Data.Fields.LinkField linkField = 
(Sitecore.Data.Fields.LinkField)drv["link"];


drv is een dataset (he ik heb de naam niet verzonnen). En de compiler weet dat er geen LinkField in zit. Dus geeft hij een compiler error. Zover niets geks.

Echter, de volgende regel compiled wel:

Sitecore.Data.Fields.LinkField linkField = 
(Sitecore.Data.Fields.Field)drv["link"];


We casten het onbekende object naar een "Field" en daarna doen we een conversie naar een LinkField (impliciete cast). Dit is raar, als je wel mag casten naar een Field en je mag daarna impliciet casten naar een LinkField dan moet de expliciete cast van het 1e statement ook mogelijk zijn. Kijkend naar de Object tree blijkt dat Field direct erft van Object en LinkField van XmlField en dat ze verder geen ouder-kind relatie hebben op welke manier dan ook. Wat wel bleek is dat de LinkField een constructor heeft waar een Field in kan. Is C# dan zo slim dat hij dit herkent en zo automatisch de conversie toe staat?

Tijd voor een test. Ik heb 3 classes en een constructor gemaakt:

public class ZonderOuder
public class Ouder

public class Kind: Ouder
public Kind(ZonderOuder aOuder)


Als mijn propositie waar is moeten de volgende regels zonder problemen compilen:

ZonderOuder objZonderOuder = new ZonderOuder();
Object o = objZonderOuder;

Kind aKind = (ZonderOuder) o;


En dat doen ze dus niet:

Cannot implicitly convert type 'CastTest.ZonderOuder' to 'CastTest.Kind'


Hiermee is bewezen dat het niet via de constructor gaat. Maar hoe dan? Het zal toch niet zo zijn dat C# het toestaat de cast operator te overloaden? Even zoeken op google geeft het resultaat. De cast operator is niet direct overloadbaar, echter kun je wel invloed uitoefen op cast (conversies) van bepaalde typen naar je class. En de volgende code doet precies dit:


public static implicit operator Kind (ZonderOuder i)
{
return new Kind();
}

Je kunt hem ook explicit maken zodat een direct cast ook mogelijk is, maar er kan er maar 1 van bestaan.

Duplicate user-defined conversion in class 'CastTest.Kind'


Ik ben oprecht geschokt. Doordat je casts kunt overloaden kun je veel vervelende trucs uithalen. Alleen al door bij een cast nieuwe objecten aan te maken kun je de == oparators onderuit halen. En daarmee wordt je code een stuk lastiger. Zeker als je een framework gebruikt waarvan je niet weet hoe het precies geïmplementeerd is kun je je veel problemen op de hals halen. Daarnaast kun je alles doen in de cast operator, desnoods je hele C-schijf wissen met een simpel statement als Kind a = (ZonderOuder) o; En dat maakt debugging een stuk lastiger.

Misschien win je een stukje schrijfbaarheid met deze constructie, maar je kunt het ook anders oplossen. Bijvoorbeeld door de constructor te gebruiken. Dan zou je krijgen:

Kind aKind = new Kind((ZonderOuder) o);

Iets meer schrijfwerk maar het houdt je code wel leesbaar en onderhoudbaarder. En kom je gek gedrag tegen dan weet je waar je moet zoeken: in de constructor. Eigenlijk win je heel weinig met deze taalconstructie, het voegt geen functionaliteit toe die je niet even makkelijk op een andere manier voor elkaar kunt krijgen, zorgt ervoor dat de code moeilijker onderhoudbaar is, en dat je weer een constructie toevoegd aan de taal die je je moet herinneren als je code gaat debuggen.

Mijn conclusie: C# bites!

3 opmerkingen:

Anoniem zei

Sirecore: porno voor Willem Alexander

Anoniem zei

Ik ben het niet 100% met je eens. Een groot voordeel van het impliciet kunnen casten van velden in Sitecore is dat je hele overzichtelijke code houdt. Wat je niet moet vergeten is dat een programmeur hiervoor als het goed is een validatie check doet. Als de programmeur dit vergeet of niet belangrijk acht, dan kun je dat niet aanrekenen als een echte fout van de API bouwer(ik zie het juist als een welkome feature), maar eerder een fout van de programmeur. Die ten alle tijden zijn binnengekomen data moet wantrouwen.

Anoniem zei

En ik ben wel heel benieuwd hoe het nu zit met de koffiepads van Douwe Egberts...