2009-04-08 6 views
3

Was für eine tolle Seite das ist, ich habe hier lauernd gelesen andere Fragen seit Jahren, aber jetzt habe ich eine eigene.Unter Verwendung von Generics wird es als Button deklariert, wird aber als internes Steuerelement für die Klasse behandelt. Warum?

Mein Kollege schrieb eine Klasse wie die untenstehende. Sobald ich es sah, wusste ich, dass es nicht funktionieren würde, aber ich habe keine Erklärung für ihn, warum es nicht funktioniert.

Was er erwartet, wenn er es als ControlItem<Button> deklariert, ist, dass die Draw (Button) -Methode aufgerufen werden würde, wenn die Base verwendet wird, um Draw() aufzurufen. Stattdessen enden wir immer damit, die Ausnahme zu werfen.

Ist dies ein Kovarianzproblem? Diese

public abstract class ControlItem 
{ 
    public ControlItem() 
    { 
    } 

    abstract public void Draw(); 
} 

public class ControlItem<T> : ControlItem where T : Control, new() 
{ 
    public T MyControl { get; set; } 

    private ControlItem() 
    {  } 

    public ControlItem(T control) 
     : base() 
    { 
     MyControl = control; 
    } 

    public override void Draw() 
    { 
     Draw(this.MyControl); 
    } 

    public void Draw(Control cntrl) 
    { 
     throw new NotImplementedException(); 
    } 

    public void Draw(Button button) 
    { 
     //Do some work 
    } 
} 

Antwort

4

Ist das ein Kovarianzproblem?

Nein, es handelt sich um ein statisches versus dynamisches Versandproblem. Statische Dispatch Mittel ein überladener Methodenaufruf in den entsprechenden Typ zur Kompilierzeit gebunden basierend auf dem Typ der variable bestand in:

class Base { } 
class Derived : Base { } 

class Foo 
{ 
    void Test() 
    { 
     Base a = new Base(); 
     Overload(a); // prints "base" 

     Derived b = new Derived(); 
     Overload(b); // prints "derived" 

     // dispatched based on c's declared type! 
     Base c = new Derived(); 
     Overload(c); // prints "base" 
    } 

    void Overload(Base obj) { Console.WriteLine("base"); } 
    void Overload(Derived obj) { Console.WriteLine("derived"); } 
} 

dynamischer Dispatch bedeutet, die Funktion zur Laufzeit auf der Grundlage der tatsächlichen Art der gebundenen das in der Variablen gespeicherte Objekt:

class Base 
{ 
    public virtual void Override() { Console.WriteLine("base"); } 
} 

class Derived : Base 
{ 
    public override void Override() { Console.WriteLine("derived"); } 
} 

class Foo 
{ 
    void Test() 
    { 
     Base a = new Base(); 
     a.Override(); // prints "base" 

     Derived b = new Derived(); 
     b.Override(); // prints "derived" 

     // dynamically dispatched based type of object stored in c! 
     Base c = new Derived(); 
     c.Override(); // prints "derived" 
    } 

    void Overload(Base obj) { Console.WriteLine("base"); } 
    void Overload(Derived obj) { Console.WriteLine("derived"); } 
} 

Der letzte Druck zeigt den Unterschied zwischen den beiden. C#, wie die meisten klassenbasierte OOP-Sprachen, unterstützt nur dynamische Dispatch für die this impliziten Parameter (bezeichnet als „single-Versand). Mit anderen Worten überschrieben Methoden dynamisch ausgelöst werden, aber überlasteten Methoden nicht.

die typische Lösung zu fälschen mehrere Versand in Einzelversand Sprachen ist durch die visitor pattern verwenden, die hier für Sie arbeiten würde

+0

OK Ich sehe Ihren Punkt und Mann diese Antworten kamen schnell. Allerdings, um Ihren Punkt zu folgen, ist es nicht das gleiche wie Ihr Beispiel. In Ihrem Fall zur Kompilierzeit ist c als Base deklariert, aber in meiner zur Kompilierzeit T ist Button, so dass es als Button MyButton deklariert ist. Wie Ihr Abgeleitetes b. –

+0

Das stimmt, aber zum Zeitpunkt der Kompilierung ist alles, was es weiß, die Einschränkung: T ist ein Control, also bindet es an die Methode, die ein Control braucht. Dies unterscheidet sich von C++ - Vorlagen, die vor der Kompilierung instanziiert werden. – munificent

+0

@munificent: +1 Sehr gut erklärt. Hat mir auch geholfen. Vielen Dank! – Codex

2

ist, da die Compiler nur sicher wissen, dass die Art, die eine Kontrolle sein wird, so wird es bindet immer auf die Methode mit dem Argumente Kontrolle. Sie müssen eine explizite Kontrolle in dem Draw() -Methode hinzufügen, wenn Sie sie anders behandeln müssen:

public override void Draw() { 
    Button btn = MyControl as Button; 
    if (btn != null) { 
     Draw(btn); 
    } else { 
     Draw(this.MyControl); 
    } 
} 

Beachten Sie, dass dies nicht sehr „generic“ ist ... aber es kann den Trick in Ihrem speziellen Fall tun .

2

auf dem munificent Antwort zu bauen.. im Gegensatz zu C++ Vorlagen C# generics are not instantiated at compile time der Code durch den C# -Compiler für einen generierte Der generische Typ ist völlig agnostisch zu den Spezialisierungen, die Sie in Ihrem Code verwenden Gibt einen Codeabschnitt aus, der für jede Substitution von Typparametern verwendet werden kann, die die Einschränkungen erfüllen. Erst wenn eine Instanz eines vollständig angegebenen generischen Typs instanziiert wird, erstellt der JIT-Compiler erst für die Laufzeit einen Code, der für Ihre Typparameter spezifisch ist.

Da der Code, die Werke für alles, was erzeugt wird, die die Kriterien der Bedingungen erfüllt, der C# -Compiler Ihr MyControl Mitglied als Variable vom Typ behandelt Control (nicht T) da, dass so viel ist, wie es von den Zwängen ableiten kann . Da der Compiler generischen Code für die Klasse ausgeben muss, muss er wählen, welche Methode aufgerufen werden soll, basierend auf was er weiß, und da es nicht sicher sein kann, ob MyControl ein Button zur Laufzeit ist, muss Draw(Control) wählen.

+0

Nun ich weiß nicht warum aber heute macht das für mich total Sinn es entscheidet natürlich zur Kompilierzeit welche Überladung zu verwenden ist. Wenn es in einer Klassenbibliothek war, weiß es nichts darüber, wie die Klasse später in einer anderen Verwendung implementiert wurde. Vielen Dank für die Hilfe. –

Verwandte Themen