2015-09-08 17 views
5

Ich muss Methodenüberladungen nach dem Typ des Objekts zur Laufzeit mit C# späten Bindungsfunktionen aufrufen. Es funktioniert gut, wenn alle Überladungen in der gleichen Klasse definiert sind, in der der Anruf stattfindet. Wenn jedoch eine Überladung in einer abgeleiteten Klasse definiert ist, wird sie zur Laufzeit nicht gebunden.C# späten Bindungsmethode Überladungen funktioniert nicht, wenn Überladung in einer abgeleiteten Klasse definiert ist

class BaseT 
{} 

class DerivedA : BaseT 
{} 

class DerivedB : BaseT 
{} 

class Generator 
{ 
    public void Generate(IEnumerable<BaseT> objects) 
    { 
     string str = ""; 
     foreach (dynamic item in objects) 
     { 
      str = str + this.Generate(item); //throws an exception on second item 
     } 
    } 

    protected virtual string Generate(DerivedA a) 
    { 
     return " A "; 
    }   
} 

class DerivedGenertor : Generator 
{ 
    protected virtual string Generate(DerivedB b) 
    { 
     return " B "; 
    } 
} 



class Program 
{ 
    static void Main(string[] args) 
    { 
     List<BaseT> items = new List<BaseT>() {new DerivedA(), new DerivedB()}; 
     var generator = new DerivedGenertor(); 
     generator.Generate(items); 
    } 
} 

Hier ist ein weiteres klares Beispiel:

class BaseT 
{} 

class DerivedA : BaseT 
{} 

class DerivedB : BaseT 
{} 

class DerivedC : BaseT 
{ } 

class Generator 
{ 
    public void Generate(IEnumerable<BaseT> objects) 
    { 
     string str = ""; 
     foreach (dynamic item in objects) 
     { 
      str = str + this.Generate(item); //throws an exception on third item 
     } 
    } 

    public virtual string Generate(DerivedA a) 
    { 
     return " A "; 
    } 

    public virtual string Generate(DerivedC c) 
    { 
     return " C "; 
    } 
} 

class DerivedGenertor : Generator 
{ 
    public virtual string Generate(DerivedB b) 
    { 
     return " B "; 
    } 
} 



class Program 
{ 
    static void Main(string[] args) 
    { 
     List<BaseT> items = new List<BaseT>() {new DerivedA(), new DerivedC(), new DerivedB()}; 
     dynamic generator = new DerivedGenertor(); 
     generator.Generate(items); 
    } 
} 
+3

'DerivedGenertor.Generate' ist" geschützt ", daher ist es nicht von der' Generator' Klasse aus zugänglich. – PetSerAl

+0

Was genau ist der zweite Gegenstand? – MikeG

Antwort

3

Sie benötigen würden die Generator so dynamisch wie auch zu erklären, so dass Sie dynamische Auflösung auf dem Eingabeobjekt haben und über die Methode aufgerufen wird. Aber Sie müssen dazu die Zugriffsmodifikatoren auf public oder protected internal ändern, da Sie jetzt eine extern aufgelöste Methode haben.

class BaseT 
{ } 

class DerivedA : BaseT 
{ } 

class DerivedB : BaseT 
{ } 

class Generator 
{ 
    public string Generate(IEnumerable<BaseT> objects) 
    { 
     string str = ""; 
     dynamic generator = this; 
     foreach (dynamic item in objects) 
     { 
      str = str + generator.Generate(item); 
     } 
     return str; 
    } 

    protected internal virtual string Generate(DerivedA a) 
    { 
     return " A "; 
    } 
} 

class DerivedGenertor : Generator 
{ 
    protected internal virtual string Generate(DerivedB b) 
    { 
     return " B "; 
    } 
} 



class Program 
{ 
    static void Main(string[] args) 
    { 
     List<BaseT> items = new List<BaseT>() { new DerivedA(), new DerivedB() }; 
     var generator = new DerivedGenertor(); 
     string ret = generator.Generate(items); 
    } 
} 
+0

Ich sehe nicht, warum CLR die andere Überladung nicht lokalisieren kann, da der Laufzeittyp des Generators tatsächlich die korrekte Überlast hat. Durchsucht CLR nicht alle Methoden, um die richtige zu finden? Ist es von Entwurf? –

+0

@ SiamakS. Nein. Das ist die Definition des Unterschieds zwischen einer späten Bindung und einer frühen Bindung. CLR sucht nicht immer nach Methoden. DLR tut es. Sie müssen explizit sagen, dass Sie eine späte Bindung wünschen, weil es so viel langsamer und fehleranfälliger ist.Zum Zeitpunkt der Kompilierung (frühe Bindung) kennt der Compiler nur "Generate (DerivedA a)", daher verwendet er diesen. – Aron

+0

@Aron Der Compiler bindet früh nichts, weil das Argument "dynamisch" ist. Wenn es versuchen würde, zur Kompilierungszeit zu binden, würde dies einen Kompilierungsfehler ergeben, da es keinen gültigen Kandidaten gibt; Nicht jedes 'BaseT' ist ein' DerivedA'. Ändern Sie "dynamisches Element" in "var Element", und der Compiler gibt Ihnen den offensichtlichen Fehler. Die Tatsache, dass der Fehler zur Laufzeit ist, weist auf eine späte Bindung hin. – InBetween

2

Wie erwarten Sie, dass es gebunden wird? Der Compiler bindet den Aufruf this.Generate(item) an den einzigen möglichen Kandidaten: Generator.Generate(DerivedA a). Das hat nichts mit der Bindung zu tun; DerivedGenerator.Generate(DerivedB b) wird nicht als ein gültiger Kandidat betrachtet, weil Generator absolut keine Ahnung von seiner Existenz hat und Sie die Methode durch die statisch typisierte Generator Instanz this aufrufen (ich beachte, dass die Methode protected ist nicht das Problem, auch wenn es public der zweite Aufruf wäre würde versagen). Warum sollte eine Basisklasse etwas über eine neue virtuelle Methode wissen, die in einer abgeleiteten Klasse definiert ist?

Um diese Arbeit zu machen, entweder Sie virtual Generate(DerivedB) in der Basisklasse Generator und Überschreibung es in DerivedGenerator oder wenn das nicht eine Option definieren dann Sie alles zur Laufzeit lösen machen.

In Ihrem Fall, wie Brian weist darauf hin, richtig, Sie müßten die Generator Instanz dynamic auch um den Generate Aufruf DerivedGenerator.Generate wenn sachgemäßer zu ermöglichen, verbindlich zu machen. Sonst wird der Kandidatensatz nur auf die Generator begrenzt sein.

Dies wird Sie verpflichten, Ihren Code erheblich zu restrukturieren, wie Sie auch Generate mindestens internal oder internal protected machen müssen.

Auch sollte ich beachten, dass eine dynamic.Generate(dynamic) Anruf zu machen Dinge funktionieren wie ein großer Code Geruch für mich macht und Sie wahrscheinlich C# 's-Typ-System missbrauchen. Ich würde in Erwägung ziehen, Ihren Code zu einer sichereren Lösung umzuformen.

Ich empfehle Ihnen auch Eric Lipperts fantastische Serie zu lesen: Wizards and warriors zu erklären, wie einige Objekthierarchien nicht gut mit C# Typ System ausgedrückt werden können und wie Sie dynamic verwenden könnten (aber normalerweise nicht), um doppelten Versand in C# zu erreichen um einige seiner Beschränkungen zu umgehen.

+0

Dies ist ein Beispiel für eine späte Bindung, die zur Laufzeit auftritt, weil das Element bei dynamic deklariert ist. Die Überlastungen befinden sich jedoch nur in der gleichen Klasse wie der Anruf. Aber stellen Sie sicher, dass die Bindung zur Laufzeit stattfindet, andernfalls würde der Compiler sich beschweren, weil keine Überlastung BaseT akzeptiert. –

+0

@ SiamakS. Ich habe meine Antwort bearbeitet, um einige wichtige Punkte zu klären. – InBetween