[C# / .NET 3.5] Downcasten in generic lists.

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
Heren,

Ik loop een beetje te rommelen met de implementatie van een generic methode, en het vreemde is dat de meest logische methode niet lijkt te werken, terwijl de meest foute methode wel geaccepteerd wordt door de compiler (.NET 3.5)...

Wie kan mij uitleggen waarom ExecuteRetrieveMultiple1 een compile-time error veroorzaakt, terwijl ExecuteRetrieveMultiple2 wel gewoon lijkt te werken?
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
public abstract class CrmEntity
{
}

public class Contact : CrmEntity
{
}

public class Program
{

    public IList<T> ExecuteRetrieveMultiple1<T>(object query) where T : CrmEntity
    {
        T contact = (T)GetContact();    //error CS0030: Cannot convert type 'GenericTypeTest.Contact' to 'T'
                                        //Waarom niet, T is in ieder geval een CrmEntity, dus downcasten naar 
                                        //Contact zou soms moeten kunnen, toch?
        IList<T> result = new List<T>();
        if (contact != null)
            result.Add(contact);

        return result;
    }

    public IList<T> ExecuteRetrieveMultiple2<T>(object query) where T : CrmEntity
    {
        Contact contact = GetContact();
        IList<Contact> result = new List<Contact>();
        if (contact != null)
            result.Add(contact);

        return (IList<T>)result;        //Compiler klaagt niet, terwijl dit een downcast is voor elk element,
                                        //iets wat in .NET 3.5 niet zomaar zou mogen!
    }

    public Contact GetContact()
    {
        return new Contact();
    }

    static void Main(string[] args)
    {

    }
}

Wat zie ik over het hoofd?

Edit:De code is onderdeel van een mock die ik maak om een backend systeem mee te faken. De signature van de generic methode wordt dus bepaald door de interface van de backend, maar de implementatie is een mock. Vandaar de ogenschijnlijk vreemde constructie ;)

[ Voor 8% gewijzigd door MrBucket op 01-03-2010 13:27 ]


Acties:
  • 0 Henk 'm!

  • TheNameless
  • Registratie: September 2001
  • Laatst online: 07-02 21:38

TheNameless

Jazzballet is vet!

Geen matches
Stel:
C#:
1
2
3
4
5
6
7
8
9
public class Foo : CrmEntity
{
}

static void Main()
{
  Program p = new Program();
  IList<Foo> foos = p.ExecuteRetrieveMultiple1<Foo>(...);
}


Met jouw code zou dit moeten kunnen.
Maar hoe kan het resultaat van GetContact() nu in dit geval gecast worden naar Foo?

Dat gaat niet omdat Contact niet van het type T is in dit geval, omdat het type van T een Foo is.
Je bent in dit geval dan aan het cross-casten, en dat kan niet in c# (voor zo ver ik weet).

[ Voor 24% gewijzigd door TheNameless op 01-03-2010 13:12 ]

Ducati: making mechanics out of riders since 1946


Acties:
  • 0 Henk 'm!

  • ronald79
  • Registratie: Februari 2003
  • Laatst online: 12-08 13:05
Geen matches
Je zou in de eerste methode de regel

T contact = (T)GetContact();

kunnen vervangen met:
CrmEntity contact = (CrnEntity)GetContact();

of zelfs:

CrmEntity contact = GetContact();

[ Voor 28% gewijzigd door ronald79 op 01-03-2010 13:07 ]

In science one tries to tell people, in such a way as to be understood by everyone, something that no one ever knew before. But in poetry, it's the exact opposite.


Acties:
  • 0 Henk 'm!

  • Amras
  • Registratie: Januari 2003
  • Laatst online: 12:28
Geen matches
Is het niet een beetje raar dat je een generieke methode wilt maken, maar wel met specifieke objecten aan de gang gaat? Is het niet stiekem een design flaw?

Acties:
  • 0 Henk 'm!

  • boe2
  • Registratie: November 2002
  • Niet online

boe2

'-')/

Geen matches
Wat Amras zegt. Het is geen oplossing voor jouw probleemstelling, maar is het niet handiger om:
C#:
1
public IList<CrmEntity> ExecuteRetrieveMultiple1(object query)

te doen?

'Multiple exclamation marks,' he went on, shaking his head, 'are a sure sign of a diseased mind.' - Pratchett.


Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
Bedankt voor alle reacties tot zover. Om de makkelijkste vraag het eerst te beantwoorden:
Amras schreef op maandag 01 maart 2010 @ 13:09:
Is het niet een beetje raar dat je een generieke methode wilt maken, maar wel met specifieke objecten aan de gang gaat? Is het niet stiekem een design flaw?
De code is onderdeel van een mock die ik maak om een backend systeem mee te faken. De signature van de generic methode wordt dus bepaald door de interface van de backend, maar de implementatie is een mock. Vandaar de ogenschijnlijk vreemde constructie ;)

De andere antwoorden moet ik nog even op me in laten werken.

Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
ronald79 schreef op maandag 01 maart 2010 @ 13:04:
Je zou in de eerste methode de regel

T contact = (T)GetContact();

kunnen vervangen met:
CrmEntity contact = (CrnEntity)GetContact();

of zelfs:

CrmEntity contact = GetContact();
Maar dat kan alleen als ik de rest van de code ook omschrijf om CrmEntity te gebruiken ipv T:
C#:
1
2
3
4
5
6
CrmEntity contact = GetContact();
IList<CrmEntity> result = new List<CrmEntity>();
if (contact != null)
    result.Add(contact);

return (IList<T>)result;

...waarmee ik eigenlijk op het 2e codevoorbeeld uitkom, wat ik nu juist niet wilde ;)

Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
TheNameless schreef op maandag 01 maart 2010 @ 12:57:
Stel:
C#:
1
2
3
4
5
6
7
8
9
public class Foo : CrmEntity
{
}

static void Main()
{
  Program p = new Program();
  IList<Foo> foos = p.ExecuteRetrieveMultiple1<Foo>(...);
}


Met jouw code zou dit moeten kunnen.
Maar hoe kan het resultaat van GetContact() nu in dit geval gecast worden naar Foo?

Dat gaat niet omdat Contact niet van het type T is in dit geval, omdat het type van T een Foo is.
Je bent in dit geval dan aan het cross-casten, en dat kan niet in c# (voor zo ver ik weet).
Maar het hele idee van casten is toch dat je typeinformatie toevoegt die door de compiler zelf niet afgeleid kan worden, maar waarvan jij als programmeur wel weet dat het goed moet gaan?

Alleen wanneer de compiler kan afleiden dat het nooit goed kan gaan zou het een compile time fout moeten geven (zou ik verwachten, althans)

Acties:
  • 0 Henk 'm!

  • FireDrunk
  • Registratie: November 2002
  • Laatst online: 15-09 19:27
Geen matches
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
public abstract class CrmEntity 
{ 
} 

public class Contact : CrmEntity 
{ 
} 

public class Program 
{ 

    public IList<T> ExecuteRetrieveMultiple1<T>(object query) where T : CrmEntity 
    { 
        CrmEntity contact = (CrmEntity)GetContact(); 
        IList<T> result = new List<T>(); 
        if (contact != null) 
            result.Add(contact); 

        return result; 
    } 

    public Contact GetContact() 
    { 
        return new Contact(); 
    } 

    static void Main(string[] args) 
    { 

    } 
}


Mag dit niet?

Even niets...


Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
Nope, krijg een "The best overloaded match for ... has some invalid arguments" op regel 17 :)

Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
En als ik de code niet generiek maak, en overal CrmEntity gebruik ipv T, dan pikt de compiler het wel.
C#:
1
2
3
4
5
6
7
8
9
public IList<CrmEntity> ExecuteRetrieveMultiple3(object query)
{
    CrmEntity contact = GetContact();
    IList<CrmEntity> result = new List<CrmEntity>();
    if (contact != null)
        result.Add(contact);

    return result;
}

Wat is dan het verschil voor de compiler met ExecuteRetrieveMultiple1?

Acties:
  • 0 Henk 'm!

  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Geen matches
Ik vraag me af wat er in dit geval zou gebeuren:
C#:
1
2
CrmEntity e = GetContact();  // upcast
T contact = (T) e;  // downcast


Ik heb het idee dat omdat T en Contact beide subclasses zijn van CrmEntity, maar niet noodzakelijk dezelfde, dat je daarom deze foutmelding krijgt, omdat er een combinatie van upcast en downcast plaatsvindt.

Er is iets voor te zeggen om alleen maar per cast een serie van upcasts en downcasts toe te staan (maar niet gemixt). Vergelijk maar met:
C#:
1
2
3
4
String s = "";
Integer i = (Integer) s; // compile-time error?
Object o = s;
Integer j = (Integer) o; // runtime error

[ Voor 19% gewijzigd door Infinitive op 01-03-2010 13:52 . Reden: C# highlighting ]

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


Acties:
  • 0 Henk 'm!

  • Snake
  • Registratie: Juli 2005
  • Laatst online: 07-03-2024

Snake

Los Angeles, CA, USA

Geen matches
thijs_cramer schreef op maandag 01 maart 2010 @ 13:39:
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
public abstract class CrmEntity 
{ 
} 

public class Contact : CrmEntity 
{ 
} 

public class Program 
{ 

    public IList<T> ExecuteRetrieveMultiple1<T>(object query) where T : CrmEntity 
    { 
        CrmEntity contact = (CrmEntity)GetContact(); 
        IList<T> result = new List<T>(); 
        if (contact != null) 
            result.Add(contact); 

        return result; 
    } 

    public Contact GetContact() 
    { 
        return new Contact(); 
    } 

    static void Main(string[] args) 
    { 

    } 
}


Mag dit niet?
Neen want T kan nu een afgeleide van CrmEntity zijn en daarom is het niet zeker dat de CrmEntity die van GetContact komt castable naar T is.

Going for adventure, lots of sun and a convertible! | GMT-8


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Geen matches
MrBucket schreef op maandag 01 maart 2010 @ 13:47:
Wat is dan het verschil voor de compiler met ExecuteRetrieveMultiple1?
Dat is altijd een een cast naar een generieker type, dus een heel ander geval, want dat andere zou ook een cast naar een broertje of zusje kunnen zijn. Je zou wel is/as of 2 casts kunnen gebruiken denk ik, maar eigenlijk snap ik de hele methode ExecuteRetrieveMultiple niet. Er wordt maximaal 1 resultaat opgehaald, dus de naam klopt niet. Als het wel om een echte query gaat, dan lijken mij de standaard-linq methodes Cast<T> of OfType<T> de aangewezen oplossing? :p

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
Infinitive schreef op maandag 01 maart 2010 @ 13:49:
Ik vraag me af wat er in dit geval zou gebeuren:
C#:
1
2
CrmEntity e = GetContact();  // upcast
T contact = (T) e;  // downcast


Ik heb het idee dat omdat T en Contact beide subclasses zijn van CrmEntity, maar niet noodzakelijk dezelfde, dat je daarom deze foutmelding krijgt, omdat er een combinatie van upcast en downcast plaatsvindt.
Dat lijkt inderdaad het verschil te maken. :)

Maar waarom de laatste cast in ExecuteRetrieveMultiple2 dan wel geldig is ontgaat me nog steeds.

Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Geen matches
MrBucket schreef op maandag 01 maart 2010 @ 14:02:
[...]

Dat lijkt inderdaad het verschil te maken. :)
Tsja, je kan ook gewoon direct zoiets doen:
C#:
1
2
3
4
            var c = GetContact();
            return (c == null) ? 
                new List<T>() : 
                new List<T>() { (T) (object) c };

Enkel blijven dat soort constructies verdacht, en een goede mogelijkheid voor InvalidCastException's. Of bedoel je meer zoiets (zonder kans op exception, maar met kans op ongewilde lege lijsten)?
C#:
1
2
3
4
            var c = GetContact() as T;
            return (c == null) ? 
                new List<T>() : 
                new List<T>() { c };
Maar waarom de laatste cast in ExecuteRetrieveMultiple2 dan wel geldig is ontgaat me nog steeds.
@Compile time kan de compiler niet checken of een IList<Contact> naar een IList<T> kan worden gecast. @Runtime blijkt dan dat dit alleen kan als T Contact is, en krijg je bij bijvoorbeeld ExecuteRetrieveMultiple2<CrmEntity>() een InvalidCastException. Echt 'geldig' zou ik het dus niet willen noemen; die methode gaat nergens over, omdat er maar één T is waarvoor hij goed werkt. :p

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Matched: covariance
pedorus schreef op maandag 01 maart 2010 @ 17:50:
[...]
@Compile time kan de compiler niet checken of een IList<Contact> naar een IList<T> kan worden gecast. @Runtime blijkt dan dat dit alleen kan als T Contact is, en krijg je bij bijvoorbeeld ExecuteRetrieveMultiple2<CrmEntity>() een InvalidCastException. Echt 'geldig' zou ik het dus niet willen noemen; die methode gaat nergens over, omdat er maar één T is waarvoor hij goed werkt. :p
Nou, hij hoeft het ook alleen maar voor T is Contact te doen, dus daar heb ik geen moeite mee.

Maar het verbaast me dat de compiler dit uberhaupt pikt. Ik dacht altijd dat een cast van een generic collectie (zeg, IList<WebControl>) alleen maar het type collectie zelf mocht veranderen (dus naar een List<WebControl>) maar niet het generieke typeargument van die collectie (dus niet naar een IList<Control> of IList<TextBox>).
Althans, niet in .NET 3.5, maar weer wel in .NET 4.0 dankzij covariance. In sommige gevallen, dan :p

En zeker voor downcasten van het generieke type (zeg IList<WebControl> naar IList<TextBox>) kan ik me allerlei problemen voorstellen. Moet de gegenereerde IL op dat moment door de gehele lijst lopen en elk element controleren of het gecast kan worden? Kan een dure operatie zijn, zeker voor iets wat als triviaal gezien wordt...

En toch doet ExecuteRetrieveMultiple2 vermoeden dat het waarschijnlijk wel op deze manier werkt?

Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Matched: covariance
MrBucket schreef op maandag 01 maart 2010 @ 19:40:
[...]

Nou, hij hoeft het ook alleen maar voor T is Contact te doen, dus daar heb ik geen moeite mee.
Dan kun je net zo goed, of zelfs veel beter, T weglaten en dit doen:
C#:
1
public IList<Contact> ExecuteRetrieveMultiple2(object query)
Maar het verbaast me dat de compiler dit uberhaupt pikt. Ik dacht altijd dat een cast van een generic collectie (zeg, IList<WebControl>) alleen maar het type collectie zelf mocht veranderen (dus naar een List<WebControl>) maar niet het generieke typeargument van die collectie (dus niet naar een IList<Control> of IList<TextBox>).
Je kan prima twee IList<T>-interfaces implementeren, als in:
C#:
1
    public class ListInterfacesTest : IList<Contact>, IList<CrmEntity>

(en de rest kun je door VS.NET aan laten maken.) Vervolgens is zo'n cast wel degelijk zinvol, dus dit compileert en runt dan gewoon:
C#:
1
2
            IList<Contact> a = new ListInterfacesTest();
            IList<CrmEntity> b = (IList<CrmEntity>) a;
Althans, niet in .NET 3.5, maar weer wel in .NET 4.0 dankzij covariance. In sommige gevallen, dan :p
Dan moet je alsnog in of out toevoegen dacht ik - zie hier. Met IList, die beide kanten op werkt, kan dat dus niet.
En zeker voor downcasten van het generieke type (zeg IList<WebControl> naar IList<TextBox>) kan ik me allerlei problemen voorstellen. Moet de gegenereerde IL op dat moment door de gehele lijst lopen en elk element controleren of het gecast kan worden? Kan een dure operatie zijn, zeker voor iets wat als triviaal gezien wordt...

En toch doet ExecuteRetrieveMultiple2 vermoeden dat het waarschijnlijk wel op deze manier werkt?
Dat is dus niet zo. Als je dat zou willen kun je iets als .Cast<T>().ToList() doen (een kopie is nodig, anders zou het achteraf alsnog mis kunnen gaan). Dus als je nu ExecuteRetrieveMultiple2 uitvoert, dan wordt gewoon getest of het object toevallig ook een interface IList<T> implementeert. Met objecten van het type List<T2> is dat alleen zo als T gelijk is aan T2, maar met een object van het type ListInterfacesTest kan het wel. :p

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
pedorus schreef op maandag 01 maart 2010 @ 21:02:
[...]

Dan kun je net zo goed, of zelfs veel beter, T weglaten en dit doen:
C#:
1
public IList<Contact> ExecuteRetrieveMultiple2(object query)
Als ik vrij zou zijn om de method signature te kiezen had ik 'm niet zoals nu opgezet, natuurlijk. Maar de huidige signature is een gegeven, aangezien deze gedefinieerd wordt door een business service waarvoor ik nu een mock aan het schrijven ben. De business-service kan in theorie met "alle" CrmEntities overweg, maar mijn mock hoeft alleen maar Contacts terug te geven. Volgens de interface van de business service, dat wel. Capiche? :)
Dan moet je alsnog in of out toevoegen dacht ik - zie hier. Met IList, die beide kanten op werkt, kan dat dus niet.
Crap, da's waar ook. Ik heb van 't weekend toevallig hetzelfde artikel zitten lezen.
[...]

Dat is dus niet zo. Als je dat zou willen kun je iets als .Cast<T>().ToList() doen (een kopie is nodig, anders zou het achteraf alsnog mis kunnen gaan). Dus als je nu ExecuteRetrieveMultiple2 uitvoert, dan wordt gewoon getest of het object toevallig ook een interface IList<T> implementeert. Met objecten van het type List<T2> is dat alleen zo als T gelijk is aan T2, maar met een object van het type ListInterfacesTest kan het wel. :p
Ah, is dat het. En die check wordt @runtime pas gedaan?
Ik denk dat er nu het e.e.a. op z'n plek begint te vallen, thanks :)

Acties:
  • 0 Henk 'm!

  • alwinuzz
  • Registratie: April 2008
  • Laatst online: 13-09 08:46
Geen matches
Eerst casten naar (object), dan kan je casten naar wat je wil. Of het at runtime werkt weet ik niet.
C#:
1
2
3
4
5
6
7
8
9
public IList<T> ExecuteRetrieveMultiple1<T>(object query) where T : CrmEntity
{
    T contact = (T)(object)GetContact(); // geen compile error
    IList<T> result = new List<T>();
    if (contact != null)
        result.Add(contact);

    return result;
}

Acties:
  • 0 Henk 'm!

  • Snake
  • Registratie: Juli 2005
  • Laatst online: 07-03-2024

Snake

Los Angeles, CA, USA

Geen matches
alwinuzz schreef op dinsdag 02 maart 2010 @ 17:35:
Eerst casten naar (object), dan kan je casten naar wat je wil. Of het at runtime werkt weet ik niet.
C#:
1
2
3
4
5
6
7
8
9
public IList<T> ExecuteRetrieveMultiple1<T>(object query) where T : CrmEntity
{
    T contact = (T)(object)GetContact(); // geen compile error
    IList<T> result = new List<T>();
    if (contact != null)
        result.Add(contact);

    return result;
}
Werkt niet:

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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication3
{

    public abstract class CrmEntity
    {
    }

    public class Contact : CrmEntity
    {
    }

    public class Foo : CrmEntity
    {

    }

    public class Program
    {

        public IList<T> ExecuteRetrieveMultiple1<T>(object query) where T : CrmEntity
        {
            T contact = (T)(object)GetContact(); // geen compile error
            IList<T> result = new List<T>();
            if (contact != null)
                result.Add(contact);

            return result;
        }

        public IList<T> ExecuteRetrieveMultiple2<T>(object query) where T : CrmEntity
        {
            Contact contact = GetContact();
            IList<Contact> result = new List<Contact>();
            if (contact != null)
                result.Add(contact);

            return (IList<T>)result;        //Compiler klaagt niet, terwijl dit een downcast is voor elk element,
            //iets wat in .NET 3.5 niet zomaar zou mogen!
        }

        public Contact GetContact()
        {
            return new Contact();
        }

        static void Main(string[] args)
        {
            var program = new Program();
            program.ExecuteRetrieveMultiple1<Foo>(null);
        }
    }
}
Unable to cast object of type 'ConsoleApplication3.Contact' to type 'ConsoleApplication3.Foo'.
Op die regel waar jij cast naar object ;)

Lol dit is ook als eerste antwoord gepost :')

[ Voor 56% gewijzigd door Snake op 03-03-2010 09:09 ]

Going for adventure, lots of sun and a convertible! | GMT-8


Acties:
  • 0 Henk 'm!

  • alwinuzz
  • Registratie: April 2008
  • Laatst online: 13-09 08:46
Geen matches
Ik zei niet voor niks dat ik niet wist of het at runtime werkt. Ik heb de code van je eerste post in VS geplakt, nu ga je ineens Main invullen, met Foo nogwel i.p.v Contact...

Anyway, is deze discussie meer filosofisch waarom het ene niet werkt en het andere wel? Want ik had (verkeerd?) begrepen dat je je mock werkend probeerde te krijgen.

Acties:
  • 0 Henk 'm!

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Geen matches
alwinuzz schreef op woensdag 03 maart 2010 @ 13:35:
Anyway, is deze discussie meer filosofisch waarom het ene niet werkt en het andere wel? Want ik had (verkeerd?) begrepen dat je je mock werkend probeerde te krijgen.
Terechte vraag.

Ik had mijn mock in principe al werkend (want ExecuteRetrieveMultiple2 doet het prima), alleen verbaasde ik me erover dat de (in mijn ogen) meest logische oplossing niet leek te werken, terwijl de oplossing waarvan ik dacht dat het niet kon in .NET 3.5 wel bleek te werken.

Dus de vraag was meer: waarom werkt het ene niet, en het andere wel; wat gebeurt er achter de schermen?

En de conclusie blijkt te zijn dat ExecuteRetrieveMultiple1 niet compiled omdat crosscasten niet in 1 statement kan (je zou apart moeten upcasten en daarna downcasten), en de reden dat ExecuteRetrieveMultiple2 wel mag is dat @runtime wordt gekeken of het generieke typeagument voor die specifieke aanroep gelijk is aan Contact - zo niet volgt er een exception.

Of dat een filosofische insteek is, ik denk het niet ;)
Pagina: 1