[.NET (C#)] Probleem met Generics / Abstract / Extended

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • Davio
  • Registratie: November 2007
  • Laatst online: 06-01 16:46
Beste Tweakers,

Ik heb een probleem met generics, abstracte klasses en het extenden daarvan.

Dit werkt namelijk niet
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Standaardzooi, nog geen fout hier
abstract class AbstractFoo
{
    protected int x;
}

class Bar : AbstractFoo
{
    public Bar()
    {
        x = 3;
    }
}

// Nu komt het, dit werkt niet
public void blaat(Stack<AbstractFoo> s)
{
}

Stack<Bar> barstack = new Stack<Bar>();
blaat(barstack);


Ik kan de methode blaat dus niet aanroepen met een stack van Bars, terwijl Bar een subklasse van AbstractFoo is. Ik krijg dan een error dat 'ie de boel niet kan converten.

Als ik in de methode-declaratie Object s zet in plaats van Stack<AbstractFoo> s, dan werkt het wel. Het maakt trouwens niet uit of Foo een abstracte klasse is. Ook als ik er een normale klasse van maak, werkt het niet.


Het uiteindelijke doel is trouwens als volgt: Ik wil een methode hebben om een stack te vullen met een vastgesteld aantal elementen van een bepaald type. Per keer kan het type van de elementen verschillen, maar ze hebben wel allemaal dezelfde parent klasse. Daarom had ik een member gemaakt in de vorm van Stack<AbstractFoo> en die wilde ik dus vullen met elementen met als type een (per keer verschillende) subklasse van AbstractFoo.

Acties:
  • 0 Henk 'm!

  • beany
  • Registratie: Juni 2001
  • Laatst online: 12:49

beany

Meeheheheheh

Moet in AbstractFoo de variabele x niet protected zijn? Nu is ie namelijk private.

Dagelijkse stats bronnen: https://x.com/GeneralStaffUA en https://www.facebook.com/GeneralStaff.ua


Acties:
  • 0 Henk 'm!

  • Davio
  • Registratie: November 2007
  • Laatst online: 06-01 16:46
beany schreef op dinsdag 01 februari 2011 @ 13:03:
Moet in AbstractFoo de variabele x niet protected zijn? Nu is ie namelijk private.
Ik heb hem op protected staan in mijn echte code, ging hier iets te kort door de bocht, zal hem aanpassen.

Acties:
  • 0 Henk 'm!

  • beany
  • Registratie: Juni 2001
  • Laatst online: 12:49

beany

Meeheheheheh

Je blaat methode is public, de class waartoe die methode behoort, is die ook public? zo ja, dan moet je AbstractFoo en Bar ook public maken.

Dagelijkse stats bronnen: https://x.com/GeneralStaffUA en https://www.facebook.com/GeneralStaff.ua


Acties:
  • 0 Henk 'm!

  • Vedett.
  • Registratie: November 2005
  • Laatst online: 13-09 17:15
Dit is Co- en ContraVariance voor Generics. Wordt ondersteund vanaf .Net 4.

[ Voor 10% gewijzigd door Vedett. op 01-02-2011 13:12 ]


Acties:
  • 0 Henk 'm!

  • Davio
  • Registratie: November 2007
  • Laatst online: 06-01 16:46
@beany: alles is zo'n beetje public wat public moet zijn en ik krijg ook geen access errors.
@Vedett.: bij mijn project settings stond nog .Net 3.5, heb hier .Net 4.0 van gemaakt, maar krijg dezelfde error.

Edit, gelezen:
Variance will only be supported in a safe way - in fact, using the abilities that the CLR already has. So the examples I give in the book of trying to use a List<Banana> as a List<Fruit> (or whatever it was) still won't work - but a few other scenarios will.
Mooi is dat, maar heeft iemand dan een idee hoe het wel kan? Of wat ik anders kan doen dan hardnekkig proberen om dit werkend te krijgen?

[ Voor 51% gewijzigd door Davio op 01-02-2011 13:19 ]


Acties:
  • 0 Henk 'm!

  • Vedett.
  • Registratie: November 2005
  • Laatst online: 13-09 17:15
Hier staat toch een voorbeeld dat verdacht veel op jouw voorbeeld lijkt. Enkel die abstract is er niet.
http://msdn.microsoft.com/en-us/library/dd799517.aspx#Y688

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

class Base
{
    public static void PrintBases(IEnumerable<Base> bases)
    {
        foreach(Base b in bases)
        {
            Console.WriteLine(b);
        }
    }
}

class Derived : Base
{
    public static void Main()
    {
        List<Derived> dlist = new List<Derived>();

        Derived.PrintBases(dlist);
        IEnumerable<Base> bIEnum = dlist;
    }
}



Had moeten verder lezen. Helemaal onderaan dat artikel staat een lijst van Interfaces die ondersteund worden.
Stack staat er inderdaad niet tussen. Wist eigenlijk helemaal niet dat het beperkt was tot een aantal interfaces. Ik zou eens moeten uitzoeken waarom dat zo is.

[ Voor 17% gewijzigd door Vedett. op 01-02-2011 13:33 ]


Acties:
  • 0 Henk 'm!

  • Davio
  • Registratie: November 2007
  • Laatst online: 06-01 16:46
Ok, maar daar gebruiken ze IEnumerable en daarvoor werkt het wel. Stack is ook een IEnumerable en dan zou het daar dus ook mee moeten werken.

Maar of ik het er nou mee eens ben dat Stack een IEnumerable is? In dit geval ben ik er blij mee. :)

Edit: Ik wil de stack dus juist vullen in de methode en als ik er een enumerable van maak, gaat dat niet.
Dus ik moet aan het begin van de methode de IEnumerable eerst terugcasten naar Stack en dan werkt het pas; lekker omslachtig.

Edit2: Hij faalt weer als ik in de methode de IEnumerable weer terugcasten naar Stack met dezelfde error als aan het begin.

[ Voor 43% gewijzigd door Davio op 01-02-2011 13:38 ]


Acties:
  • 0 Henk 'm!

  • defcon84
  • Registratie: September 2009
  • Laatst online: 12-09 11:37

defcon84

Multipass?

C#:
1
public class Stack<T> : IEnumerable<T>, ICollection, IEnumerable


dus ja, als je de param van blaat naar een IEnumerable veranderd gaat het idd werken:
Blaat(IEnumerable<AbstractFoo> s)
maar het ligt er juist aan wat je wil doen binnen Blaat
als je alleen wil foreaching over de stack, dan zal een IEnumerable het fixe

voor 4.0 waren Class<Base> en Class<Derived> niet compatibel
in 4.0 ook niet }:O , maar het is al beter geworden want het werkt wel voor interfaces en delegates

Acties:
  • 0 Henk 'm!

  • Vedett.
  • Registratie: November 2005
  • Laatst online: 13-09 17:15
Ja, het verschil is me nu ook duidelijk. Zie mijn edit.

Wat je nog zou kunnen overwegen is dit

C#:
1
2
3
4
5
6
public static void blaat<T>(Stack<T> s) where T : AbstractFoo
{ 
    foreach(var f in s){
        Console.WriteLine(f);
    }
} 

[ Voor 6% gewijzigd door Vedett. op 01-02-2011 13:37 ]


Acties:
  • 0 Henk 'm!

  • apokalypse
  • Registratie: Augustus 2004
  • Laatst online: 14-09 00:16
[b][message=35465947,noline]
Edit: Ik wil de stack dus juist vullen in de methode en als ik er een enumerable van maak, gaat dat niet.
Dus ik moet aan het begin van de methode de IEnumerable eerst terugcasten naar Stack en dan werkt het pas; lekker omslachtig.

Edit2: Hij faalt weer als ik in de methode de IEnumerable weer terugcasten naar Stack met dezelfde error als aan het begin.
Met vullen wil je contravariantie! En IEnumerable is Covariant (zeg maar 'lezen', niet geheel correct)

Mogelijke oplossing: eigen interface maken die contravariant is (voor zijn type variabele), en deze aan een custom Stack klasse hangen (eventueel inheritance). Helaas moet je dan werken met je eigen Stack klasse.

[ Voor 12% gewijzigd door apokalypse op 01-02-2011 13:44 ]


Acties:
  • 0 Henk 'm!

  • Davio
  • Registratie: November 2007
  • Laatst online: 06-01 16:46
Vedett.: Dat werkt ook niet helemaal; ik heb het nu zo:

C#:
1
2
3
4
5
6
7
8
9
private void initStack<T>(IEnumerable<AbstractFoo> foos) where T: AbstractFoo, new()
{
    Stack<AbstractFoo> s = (Stack<AbstractFoo>)foos; // Loopt hier fout met can not convert error
    for(int i = 0; i < 10; ++i)
    {
        T bla = new T();
        s.Push(bla);
    }
}


Edit: Een custom stack-klasse maken lukt nog wel, maar hoe maak ik een contravariante interface?

Edit 2: Ik kan het altijd nog uitklappen en elke stack (zijn er ong 15) apart vullen (in elke Bar-klasse een methode GetStack(int count), maar het leek me juist zo mooi om dit met een generic methode te doen...

[ Voor 29% gewijzigd door Davio op 01-02-2011 13:47 ]


Acties:
  • 0 Henk 'm!

  • Vedett.
  • Registratie: November 2005
  • Laatst online: 13-09 17:15
Ja, maar dan moet je parameter geen IEnumerable meer zijn maar kan je terug met een Stack<T> werken!

C#:
1
2
3
4
5
6
7
8
9
10
11
private void initStack<T>(Stack<T> foos) where T: AbstractFoo, new() 
{ 
    //casten hoeft niet meer
    //Stack<AbstractFoo> s = (Stack<AbstractFoo>)foos; // Loopt hier fout met can not convert error 

    for(int i = 0; i < 10; ++i) 
    { 
        T bla = new T(); 
        s.Push(bla); 
    } 
} 

[ Voor 62% gewijzigd door Vedett. op 01-02-2011 13:48 ]


Acties:
  • 0 Henk 'm!

  • apokalypse
  • Registratie: Augustus 2004
  • Laatst online: 14-09 00:16
Davio schreef op dinsdag 01 februari 2011 @ 13:42:

Edit: Een custom stack-klasse maken lukt nog wel, maar hoe maak ik een contravariante interface?

Edit 2: Ik kan het altijd nog uitklappen en elke stack (zijn er ong 15) apart vullen, maar het leek me juist zo mooi om dit met een generic methode te doen...
met het in keyword.

even uit het hoofd
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//eventueel public modifiers toevoegen.

interface IPushable<in T> //contravariance in T.
{
    void Push(T item); //void?
}
class MyStack<T>: Stack<T>, IPushable<T>{ }

private void initStack<T>(IPushable<T> stack)
{
    ...
    stack.Push(...); //Push zit in de interface IPushable<T>
    ...
}

//ergens in de 'main' methode.
var myStack = new MyStack();
initStack(myStack); 



maaarrrr, wil je deze alleen vullen met new T(), dan zou ik het niet zo oplossen.

offtopic:
Jammer hè, dat je geen interface kan binden aan bestaande klassen :'(
Vooral icm extentionsmethods zou ik dat graag willen.

[ Voor 23% gewijzigd door apokalypse op 01-02-2011 13:54 ]


Acties:
  • 0 Henk 'm!

  • Davio
  • Registratie: November 2007
  • Laatst online: 06-01 16:46
Ok, het is uiteindelijk gelukt via Vedett.s voorbeeld.

Ik heb het idee losgelaten om als parameter in de methode een Stack<AbstractFoo> te eisen, maar een Stack met hetzelfde type T als waar de methode mee is aangeroepen.

Bedankt voor jullie reacties, ik heb er veel van geleerd.

Edit: Ik zit nog wel met het volgende probleem...

Ik heb een Stack als member in mijn klasse met AbstractFoos
C#:
1
2
3
4
5
6
7
8
9
10
public class Whatever
{
    public Stack<AbstractFoo> s;
    
    public Whatever()
    {
        s = new Stack<Bar>(); // So far so good
        initStack<Bar>(s); // Werkt dan weer niet
    }
}


Het werkt dus alleen als ik bij de declaratie al precies weet welke type objecten er in de stack gaan, maar dat weet ik niet. Ik weet alleen dat het subtypes van AbstractFoo zijn en dan kom ik toch weer op hetzelfde probleem uit...

Ga dus eens het onderste voorbeeld proberen.

Update: Ik heb het hele Generics-idee maar laten varen, maak nu gebruik van een simpele Stack, zonder <T>, cast de boel wel elke keer terug.

[ Voor 59% gewijzigd door Davio op 01-02-2011 14:12 ]


Acties:
  • 0 Henk 'm!

  • Vedett.
  • Registratie: November 2005
  • Laatst online: 13-09 17:15
Als je weet dat je class een stack beheert die objecten bevat van het type AbstractFoo, waarom zou je dan elders in die class Bar objecten willen gaan aanmaken?

Volgens mij zit er iets mis in de structuur van ja hele class. Moet je misschien Whatever ook generic maken?
Kan je heel de class eens posten? Dan wordt de puzzel misschien wat duidelijker.

Acties:
  • 0 Henk 'm!

  • FireDrunk
  • Registratie: November 2002
  • Laatst online: 12:07
Je wil dus verschillende implementaties van AbstractFoo samen in een Stack kunnen stoppen? En om te zorgen dat je het object zelf kan pushen ga je die IPushable maken?
Die functionaliteit zit toch buiten de klasse AbstractFoo/Bar? Waarom ga je het object aware maken van de container waar hij in zit?

Even niets...


Acties:
  • 0 Henk 'm!

  • Davio
  • Registratie: November 2007
  • Laatst online: 06-01 16:46
Het idee was aanvankelijk om een Stack te hebben met objecten van een abstract type.

In die stack zou ik dan objecten pushen die een subtype waren van dat abstracte type. Op voorhand (compile time) zou ik niet precies weten welk type objecten ik daadwerkelijk in de stack zou pushen, pas tijdens runtime.

Daarom kwam ik bij Generics uit.

Uiteindelijk heb ik ervoor gekozen om gewoon een Stack te nemen (System.Collections.Stack ipv System.Collections.Generics.Stack<T>), omdat ik die kan vullen met willekeurige objecten.

Ik was misschien een beetje in de war met Generics, omdat dat een oplossing is waarbij je wel compile-time kennis van de types nodig hebt. Echter compileert hij speciaal voor jouw types de goede methodes.

Ik wilde dus niet verschillende implementaties van AbstractFoo samen in dezelfde stack stoppen (misschien kan het daarom ook niet), maar allemaal dezelfde types, waarbij het specifieke type elke keer anders was.

Acties:
  • 0 Henk 'm!

  • Wijnbo
  • Registratie: December 2002
  • Laatst online: 06-09 20:35

Wijnbo

Electronica werkt op rook.

Hier mijn probleem topics, waar ik ongeveer het zelfde had ;)

\[.NET] Waarom mag dit niet (inheritance)

\[.NET] IList<T> en casten naar interfaces

[ Voor 21% gewijzigd door Wijnbo op 03-02-2011 09:23 ]

Pagina: 1