C # optionale Parameter für überschriebene Methoden

74

In .NET Framework scheint es ein Problem mit optionalen Parametern zu geben, wenn Sie die Methode überschreiben. Die Ausgabe des folgenden Codes lautet: "bbb" "aaa". Aber die Ausgabe, die ich erwarte, ist: "bbb" "bbb". Gibt es eine Lösung dafür. Ich weiß, dass es mit Methodenüberladung gelöst werden kann, frage mich aber den Grund dafür. Auch der Code funktioniert gut in Mono.

class Program
{
    class AAA
    {
        public virtual void MyMethod(string s = "aaa")
        {
            Console.WriteLine(s);
        }

        public virtual void MyMethod2()
        {
            MyMethod();
        }
    }

    class BBB : AAA
    {
        public override void MyMethod(string s = "bbb")
        {
            base.MyMethod(s);
        }

        public override void MyMethod2()
        {
            MyMethod();
        }
    }

    static void Main(string[] args)
    {
        BBB asd = new BBB();
        asd.MyMethod();
        asd.MyMethod2();
    }
}
SARI
quelle
6
Optionale Parameter sind schlecht, mmkay! TBH, ich habe mich noch nie darum gekümmert, sie zu benutzen.
Leppie
40
@ leppie, es ist sehr nützliche Funktion. Eine Funktion in allen Szenarien abzulehnen ist nicht ratsam.
Gdoron unterstützt Monica
1
Ich bin neugierig zu wissen, ob das auch in VB.Net passiert und nicht spezifisch für den C # -Compiler ist. Muss überprüft werden.
VSS
2
Das klingt nach einem Fehler in Mono.
Jon Hanna
4
"Ich habe nicht gesagt, dass ich die Funktion in allen Szenarien ablehne" - natürlich haben Sie das getan. "Ich bin mir nicht sicher, wie Sie zu diesem Schluss gekommen sind" - äh, "Optionale Parameter sind schlecht, mmkay!"
Jim Balter

Antworten:

25

Bemerkenswert ist hier, dass die überschriebene Version jedes Mal aufgerufen wird. Ändern Sie die Überschreibung in:

public override void MyMethod(string s = "bbb")
{
  Console.Write("derived: ");
  base.MyMethod(s);
}

Und die Ausgabe ist:

derived: bbb
derived: aaa

Eine Methode in einer Klasse kann eine oder zwei der folgenden Aktionen ausführen:

  1. Es definiert eine Schnittstelle, über die anderer Code aufgerufen werden kann.
  2. Es definiert eine Implementierung, die beim Aufruf ausgeführt werden soll.

Es kann nicht beides tun, da eine abstrakte Methode nur die erstere tut.

Innerhalb BBBdes Aufrufs MyMethod()ruft eine Methode definiert in AAA.

Da es eine Überschreibung gibt BBB, führt der Aufruf dieser Methode dazu, dass eine Implementierung BBBaufgerufen wird.

Die Definition in AAAinformiert den aufrufenden Code über zwei Dinge (nun, einige andere, die hier keine Rolle spielen).

  1. Die Unterschrift void MyMethod(string).
  2. (Für die Sprachen, die dies unterstützen) Der Standardwert für den einzelnen Parameter ist. "aaa"Wenn beim Kompilieren des Codes des Formulars MyMethod()keine Methodenübereinstimmung MyMethod()gefunden werden kann, können Sie ihn durch einen Aufruf von "MyMethod" ("aaa") ersetzen.

Das macht der Aufruf BBBalso so: Der Compiler sieht einen Aufruf von MyMethod(), findet keine Methode MyMethod(), findet aber eine Methode MyMethod(string). Es wird auch angezeigt, dass an der Stelle, an der es definiert ist, der Standardwert "aaa" vorhanden ist. Bei der Kompilierung wird dies in einen Aufruf von geändert MyMethod("aaa").

Von innen BBB, AAAist der Ort , an dem als AAA‚s Methoden definiert werden, auch außer Kraft gesetzt , wenn in BBB, so dass sie können außer Kraft gesetzt werden.

Wird zur Laufzeit MyMethod(string)mit dem Argument "aaa" aufgerufen. Da es ein überschriebenes Formular gibt, wird dieses Formular aufgerufen, aber es wird nicht mit "bbb" aufgerufen, da dieser Wert nichts mit der Laufzeitimplementierung zu tun hat, sondern mit der Definition der Kompilierungszeit.

Durch this.das Hinzufügen wird geändert, welche Definition untersucht wird, und somit wird geändert, welches Argument im Aufruf verwendet wird.

Edit: Warum mir das intuitiver erscheint.

Persönlich und da ich von dem spreche, was intuitiv ist, kann es nur persönlich sein, finde ich dies aus folgendem Grund intuitiver:

Wenn ich BBBdann programmieren MyMethod(string)würde, egal ob ich anrufe oder überschreibe , würde ich das als " AAASachen machen" betrachten - es ist BBB" AAASachen machen", aber es macht trotzdem AAASachen. Unabhängig davon, ob ich anrufe oder überschreibe, werde ich mir der Tatsache bewusst sein, dass es AAAso definiert war MyMethod(string).

Wenn ich den verwendeten Code aufrufen BBBwürde, würde ich daran denken, " BBBSachen zu verwenden". Ich bin mir möglicherweise nicht sehr bewusst, in was ursprünglich definiert wurde AAA, und ich würde dies möglicherweise nur als Implementierungsdetail betrachten (wenn ich nicht auch die AAASchnittstelle in der Nähe verwenden würde).

Das Verhalten des Compilers entspricht meiner Intuition, weshalb ich beim ersten Lesen der Frage den Eindruck hatte, dass Mono einen Fehler hatte. Bei Betrachtung kann ich nicht sehen, wie einer das angegebene Verhalten besser erfüllt als der andere.

Im Übrigen würde ich, während ich auf persönlicher Ebene bleibe, niemals optionale Parameter mit abstrakten, virtuellen oder überschriebenen Methoden verwenden, und wenn jemand anderes dies überschreibt, würde ich dessen entsprechen.

Jon Hanna
quelle
+1 Gute Beobachtung. Was passiert, wenn beide Standardeinstellungen gleich sind?
Leppie
2
@leppie Wenn beide Standardeinstellungen gleich sind, wird der Unterschied unbedeutend, da beide die gleichen Ergebnisse erzielen und der syntaktische Zucker genauso süß schmeckt, wie Sie es erwarten. Ich stelle fest, dass mindestens ein Tool dies als Empfehlung hat. Persönlich würde ich mich von den Standardeinstellungen für Virtual vollständig fernhalten.
Jon Hanna
Ich habe eine Antwort hinzugefügt, die auf die Spezifikation verweist ... Gedanken?
Marc Gravell
1
Ich denke immer noch, dass Ihre Ergebnisse ziemlich genau zeigen, dass dies ein echter Fehler ist.
Leppie
1
Deine Gedanken zu "diesem". Das Ändern der aufgelösten Methode ist ... nicht offensichtlich. Bitte geben Sie eine Spezifikationsreferenz an, um dieses Argument zu unterstützen. Es stimmt nicht mit der Spezifikation gemäß 7.5.1.1 und 7.5.1.2 überein.
Marc Gravell
37

Sie können eindeutig sagen:

this.MyMethod();

(in MyMethod2())

Ob es sich um einen Fehler handelt, ist schwierig. es sieht jedoch inkonsistent aus. Resharper warnt Sie einfach davor, Änderungen am Standardwert in einem Override vorzunehmen, wenn dies hilft: p Resharper teilt Ihnen natürlich auch mit , dass das this.redundant ist, und bietet an, es für Sie zu entfernen ... was das Verhalten ändert - also auch Resharper ist nicht perfekt.

Es sieht so aus, als könnte es sich als Compiler-Fehler qualifizieren, das gebe ich Ihnen zu. Ich müsste wirklich genau hinschauen , um sicher zu sein ... wo ist Eric, wenn du ihn brauchst?


Bearbeiten:

Der entscheidende Punkt hier ist die Sprachspezifikation; Schauen wir uns §7.5.3 an:

Beispielsweise enthält die Gruppe der Kandidaten für einen Methodenaufruf keine Methoden, die als überschrieben markiert sind (§7.4), und Methoden in einer Basisklasse sind keine Kandidaten, wenn eine Methode in einer abgeleiteten Klasse anwendbar ist (§7.6.5.1).

(und tatsächlich lässt §7.4 overrideMethoden eindeutig außer Betracht)

Hier gibt es einen Konflikt ... es heißt, dass die Basismethoden nicht verwendet werden, wenn es eine anwendbare Methode in einer abgeleiteten Klasse gibt - was uns zur abgeleiteten Methode führen würde, aber gleichzeitig heißt es, dass markierte Methoden dies overridenicht sind berücksichtigt.

In §7.5.1.1 heißt es dann jedoch:

Bei virtuellen Methoden und Indexern, die in Klassen definiert sind, wird die Parameterliste aus der spezifischsten Deklaration oder Überschreibung des Funktionselements ausgewählt, beginnend mit dem statischen Typ des Empfängers und Durchsuchen seiner Basisklassen.

und dann erklärt §7.5.1.2, wie die Werte zum Zeitpunkt des Aufrufs ausgewertet werden:

Während der Laufzeitverarbeitung eines Funktionselementaufrufs (§7.5.4) werden die Ausdrücke oder Variablenreferenzen einer Argumentliste wie folgt von links nach rechts ausgewertet:

... (schnipsen) ...

Wenn Argumente in einem Funktionselement mit entsprechenden optionalen Parametern weggelassen werden, werden die Standardargumente der Funktionselementdeklaration implizit übergeben. Da diese immer konstant sind, hat ihre Bewertung keinen Einfluss auf die Bewertungsreihenfolge der verbleibenden Argumente.

Dies unterstreicht ausdrücklich, dass es sich um die Argumentliste handelt, die zuvor in §7.5.1.1 als aus der spezifischsten Deklaration oder Überschreibung stammend definiert wurde . Es erscheint vernünftig, dass dies die "Methodendeklaration" ist, auf die in §7.5.1.2 Bezug genommen wird. Daher sollte der übergebene Wert vom am meisten abgeleiteten bis zum statischen Typ sein.

Dies würde bedeuten: csc hat einen Fehler und sollte die abgeleitete Version ("bbb bbb") verwenden, es sei denn, es ist darauf beschränkt (über base.oder Casting auf einen Basistyp), die Deklarationen der Basismethode zu betrachten (§7.6.8) ).

Marc Gravell
quelle
Übrigens, setzt sich dieses Verhalten nur bei Vorhandensein optionaler Parameter aus?
Leppie
@leppie, ja, da es nur um die Behandlung optionaler Parameter bei der Kompilierung geht, setzt es sich nur in Gegenwart optionaler Parameter aus. Wenn Sie glauben, dass es sich um einen Fehler handelt, können Sie irgendwo in der Spezifikation feststellen, dass dies nicht passieren sollte? Ich kann so oder so nichts darauf finden. Ich werde meine Antwort bearbeiten, um hinzuzufügen, warum dies für mich das intuitivere Verhalten ist.
Jon Hanna
@ JonHanna: Ich vermute, dass die Laufzeit nicht mit den Methodensignaturen übereinstimmt, da für den optionalen Parameter eine andere Kompilierungszeitkonstante vorhanden ist. Klingt vernünftig?
Leppie
@leppie Runtime ruft immer das Abgeleitete auf, dieser Teil ist eindeutig korrekt. Der umstrittene Punkt ist über das, womit es es nennen sollte.
Jon Hanna
@leppie - eine Spezifikationsreferenz hinzugefügt; Gedanken?
Marc Gravell
15

Das sieht für mich nach einem Fehler aus. Ich glaube, es ist gut spezifiziert und sollte sich genauso verhalten, als ob Sie die Methode mit dem expliziten thisPräfix aufrufen .

Ich habe das Beispiel vereinfacht, um nur eine einzige virtuelle Methode zu verwenden, und sowohl gezeigt, welche Implementierung aufgerufen wird, als auch wie hoch der Parameterwert ist:

using System;

class Base
{
    public virtual void M(string text = "base-default")
    {
        Console.WriteLine("Base.M: {0}", text);
    }   
}

class Derived : Base
{
    public override void M(string text = "derived-default")
    {
        Console.WriteLine("Derived.M: {0}", text);
    }

    public void RunTests()
    {
        M();      // Prints Derived.M: base-default
        this.M(); // Prints Derived.M: derived-default
        base.M(); // Prints Base.M: base-default
    }
}

class Test
{
    static void Main()
    {
        Derived d = new Derived();
        d.RunTests();
    }
}

Wir müssen uns also nur um die drei Aufrufe in RunTests kümmern. Die wichtigen Bits der Spezifikation für die ersten beiden Aufrufe sind Abschnitt 7.5.1.1, in dem die Parameterliste erläutert wird, die beim Auffinden entsprechender Parameter verwendet werden soll:

Bei virtuellen Methoden und Indexern, die in Klassen definiert sind, wird die Parameterliste aus der spezifischsten Deklaration oder Überschreibung des Funktionselements ausgewählt, beginnend mit dem statischen Typ des Empfängers und Durchsuchen seiner Basisklassen.

Und Abschnitt 7.5.1.2:

Wenn Argumente in einem Funktionselement mit entsprechenden optionalen Parametern weggelassen werden, werden die Standardargumente der Funktionselementdeklaration implizit übergeben.

Der "entsprechende optionale Parameter" ist das Bit, das 7.5.2 mit 7.5.1.1 verbindet.

Für beide M()und this.M(), dass Parameterliste der in sein sollte Derivedals statischer Typ des Empfängers ist Derived, die Tat kann man sagen , dass die Compiler behandelt , dass die Parameterliste weiter oben in der Zusammenstellung, als ob Sie die Parameter machen obligatorisch in Derived.M(), sowohl von Die Aufrufe schlagen fehl - für den M()Aufruf muss der Parameter einen Standardwert haben Derived, dieser wird jedoch ignoriert!

Tatsächlich wird es noch schlimmer: wenn Sie einen Standardwert für die Parameter in bereitstellen , Derivedaber es zwingend machen Base, die Anrufende M()bis Verwendung nullals Argument Wert. Wenn nichts anderes, würde ich sagen, dass dies ein Fehler ist: Dieser nullWert kann nicht von irgendwoher kommen, wo er gültig ist. ( nullDies liegt daran, dass dies der Standardwert des stringTyps ist. Es wird immer nur der Standardwert für den Parametertyp verwendet.)

Abschnitt 7.6.8 der Spezifikation befasst sich mit base.M (), das besagt, dass neben dem nicht virtuellen Verhalten der Ausdruck als betrachtet wird ((Base) this).M(); Daher ist es völlig korrekt, dass die Basismethode verwendet wird, um die effektive Parameterliste zu bestimmen. Das heißt, die letzte Zeile ist korrekt.

Nur um es allen einfacher zu machen, die den oben beschriebenen wirklich seltsamen Fehler sehen möchten, bei dem ein Wert verwendet wird, der nirgendwo angegeben ist:

using System;

class Base
{
    public virtual void M(int x)
    {
        // This isn't called
    }   
}

class Derived : Base
{
    public override void M(int x = 5)
    {
        Console.WriteLine("Derived.M: {0}", x);
    }

    public void RunTests()
    {
        M();      // Prints Derived.M: 0
    }

    static void Main()
    {
        new Derived().RunTests();
    }
}
Jon Skeet
quelle
Zu "gut spezifiziert" - können Sie sich die winzige Änderung ansehen, die ich an meiner Antwort bezüglich des Wortlauts in §7.5.1.2 vorgenommen habe - es scheint ein wenig mehrdeutig ...?
Marc Gravell
@MarcGravell: Ich glaube nicht - der Teil, der über "fehlende" Argumente spricht, besagt ausdrücklich, dass der Wert aus dem entsprechenden Parameter stammt , der in 7.5.1.1 angegeben ist. Das Funktionselement ist also die Basisdeklaration, aber die Liste der entsprechenden Parameter für diesen Aufruf wird in der Überschreibung angegeben. Zumindest denke ich, dass dies eine vernünftige Art ist, es zu lesen.
Jon Skeet
Ah ja; Vielleicht ist es richtig anzunehmen, dass "Methodendeklaration" die Deklaration ist, die für die Argumentliste (7.5.1.1) verwendet wird, und nicht die Deklaration, die für die Überlastungsauflösung (7.4) verwendet wird. In jedem Fall sind das Verhalten von mit / ohne this.und die erforderlichen / optionalen Kuriositäten, die Sie hervorheben, alle ernsthaft seltsam und sehen sehr kaputt aus.
Marc Gravell
Die Auswirkungen dieses möglichen Fehlers sind für mich trübe. Ich möchte Ihre allgemeinen Empfehlungen für eine "Best Practice" (für weniger fortgeschrittene Entwickler) erhalten, um mögliche Fallstricke in diesem Bereich zu vermeiden. Ich habe hier eine entsprechende Frage gestellt: stackoverflow.com/questions/9381850/… . Würden Sie so freundlich sein, eine Antwort auf diese Frage zu posten?
kmote
@kmote: Ich glaube, Eric Lippert hat bereits eine ganze Reihe von Empfehlungen zu optionalen Parametern - siehe seinen Blog. Ich kann nicht sagen, dass ich sie genug benutzt habe, um alle relevanten Fallstricke zu entdecken.
Jon Skeet
10

Hast du es versucht:

 public override void MyMethod2()
    {
        this.MyMethod();
    }

Sie weisen Ihr Programm also tatsächlich an, die Überschreibungsmethode zu verwenden.

Basti
quelle
thisist impliziert. Somit ist Ihre Antwort überflüssig.
Leppie
Wenn das der Grund ist, dann haben wir wirklich einen Fehler, da dies der Standard ohne ist base.
Christian.K
5
@ leppie es ändert jedoch wirklich das Ergebnis; p
Marc Gravell
2
Die Verwendung dieses Schlüsselworts muss eine bewährte Methode sein <immer>
VSS
3
@MarcGravell: Dann ist das ein großer Fehler!
Leppie
9

Das Verhalten ist definitiv sehr seltsam; Mir ist nicht klar, ob es sich tatsächlich um einen Fehler im Compiler handelt, aber es könnte sein.

Der Campus hat letzte Nacht ziemlich viel Schnee bekommen und Seattle ist nicht sehr gut im Umgang mit Schnee. Mein Bus fährt heute Morgen nicht, daher kann ich nicht ins Büro, um zu vergleichen, was C # 4, C # 5 und Roslyn zu diesem Fall sagen und ob sie nicht übereinstimmen. Ich werde später in dieser Woche versuchen, eine Analyse zu veröffentlichen, sobald ich wieder im Büro bin und die richtigen Debugging-Tools verwenden kann.

Eric Lippert
quelle
Ausreden, Ausreden, Ausreden: p (nur ein Scherz) Ich freue mich auf die Ergebnisse.
Leppie
... sagte er, als er seine Snowboardschuhe anzog und zum Berg ging.
Fernando
sehr unterhaltsame Diskussion über diese Frage .. :)
VSS
1
Was besonders seltsam aussieht, ist der Unterschied zwischen this.MyMethod () und einfach MyMethod (). Viel Glück mit dem Schnee.
Marc Gravell
2
Klingeln! Hast du darüber gebloggt und ich habe es verpasst? In jedem Fall wäre ein Link zu Ihrer Analyse aus dieser Antwort sehr schön.
Ben Voigt
5

Möglicherweise liegt dies an Mehrdeutigkeiten, und der Compiler räumt der Basis- / Superklasse Priorität ein. Die folgende Änderung des Codes Ihrer Klasse BBB unter Hinzufügung eines Verweises auf das thisSchlüsselwort ergibt die Ausgabe 'bbb bbb':

class BBB : AAA
{
    public override void MyMethod(string s = "bbb")
    {
        base.MyMethod(s);
    }

    public override void MyMethod2()
    {
        this.MyMethod(); //added this keyword here
    }
}

Dies impliziert unter anderem, dass Sie das thisSchlüsselwort immer verwenden sollten, wenn Sie Eigenschaften oder Methoden für die aktuelle Instanz der Klasse als Best Practice aufrufen .

Ich wäre besorgt, wenn diese Mehrdeutigkeit in der Basis- und der untergeordneten Methode nicht einmal eine Compiler-Warnung auslösen würde (wenn nicht ein Fehler), aber wenn dies der Fall ist, war das vermutlich unsichtbar.

================================================== ================

BEARBEITEN: Betrachten Sie die folgenden Beispielauszüge aus diesen Links:

http://geekswithblogs.net/BlackRabbitCoder/archive/2011/07/28/c.net-little-pitfalls-default-parameters-are-compile-time-substitutions.aspx

http://geekswithblogs.net/BlackRabbitCoder/archive/2010/06/17/c-optional-parameters---pros-and-pitfalls.aspx

Fallstricke: Optionale Parameterwerte sind zur Kompilierungszeit Es gibt eine Sache und eine Sache, die nur zu beachten ist, wenn optionale Parameter verwendet werden. Wenn Sie diese eine Sache im Auge behalten, besteht die Möglichkeit, dass Sie mögliche Fallstricke bei ihrer Verwendung gut verstehen und vermeiden: Diese eine Sache ist folgende: Optionale Parameter sind syntaktischer Zucker zur Kompilierungszeit!

Fallstricke: Achten Sie bei der Vererbung und Schnittstellenimplementierung auf Standardparameter

Die zweite potenzielle Gefahr besteht nun in der Vererbung und der Implementierung der Schnittstelle. Ich werde mit einem Puzzle veranschaulichen:

   1: public interface ITag 
   2: {
   3:     void WriteTag(string tagName = "ITag");
   4: } 
   5:  
   6: public class BaseTag : ITag 
   7: {
   8:     public virtual void WriteTag(string tagName = "BaseTag") { Console.WriteLine(tagName); }
   9: } 
  10:  
  11: public class SubTag : BaseTag 
  12: {
  13:     public override void WriteTag(string tagName = "SubTag") { Console.WriteLine(tagName); }
  14: } 
  15:  
  16: public static class Program 
  17: {
  18:     public static void Main() 
  19:     {
  20:         SubTag subTag = new SubTag();
  21:         BaseTag subByBaseTag = subTag;
  22:         ITag subByInterfaceTag = subTag; 
  23:  
  24:         // what happens here?
  25:         subTag.WriteTag();       
  26:         subByBaseTag.WriteTag(); 
  27:         subByInterfaceTag.WriteTag(); 
  28:     }
  29: } 

Was geschieht? Nun, obwohl das Objekt jeweils SubTag ist, dessen Tag "SubTag" ist, erhalten Sie:

1: SubTag 2: BaseTag 3: ITag

Aber denken Sie daran, sicherzustellen, dass Sie:

Fügen Sie keine neuen Standardparameter in die Mitte eines vorhandenen Satzes von Standardparametern ein. Dies kann zu unvorhersehbarem Verhalten führen, das nicht unbedingt einen Syntaxfehler auslöst - am Ende der Liste hinzufügen oder eine neue Methode erstellen. Seien Sie äußerst vorsichtig, wie Sie Standardparameter in Vererbungshierarchien und -schnittstellen verwenden. Wählen Sie die am besten geeignete Ebene aus, um die Standardeinstellungen basierend auf der erwarteten Verwendung hinzuzufügen.

================================================== ========================

VSS
quelle
1
Sie können sicherlich Ihre eigenen Richtlinien und Best Practices auswählen, aber ich möchte meinen Code lieber nicht mit (ansonsten) unnötigen Schlüsselwörtern verunreinigen (obwohl mich dieser spezielle Fall ebenfalls beunruhigt).
Christian.K
@ Christian.KI würde mir, anderen (Lesen des Codes) und dem Compiler lieber sagen, welche Methode / Eigenschaft immer verwendet werden soll, als jemanden zum Nachdenken und Auswählen zu bringen, was einen Overhead und jetzt die Mehrdeutigkeit verursacht.
VSS
2
Das Aufrufen von redundantem Code als „Best Practice“ ist eher zweifelhaft. Sie brauchen nicht den Compiler (oder Leser) zu sagen , welches Objekt Sie die Methode aufrufen, du schon sind dies zu tun, da thisangedeutet wird (nie diese Marotte ausmacht).
Konrad Rudolph
@KonradRudolph Ich denke, Sie müssen manchmal redundant sein, um Ihren Code lesbarer und leicht verständlich zu machen .. aber nicht sicher, ich lerne immer noch ..
VSS
1
Ich finde, die zusätzliche Ausführlichkeit von häufig this.macht Code weniger lesbar und schwerer zu verstehen. Das Ändern optionaler Parameter bei Überschreibungen wird von einigen Tools bereits als nicht bewährte Methode erkannt, und dies scheint klüger zu verbieten (optionale Parameter für abstrakte, virtuelle oder überschriebene Methoden erscheinen mir in der Tat unklug).
Jon Hanna
1

Dies liegt meiner Meinung nach daran, dass diese Standardwerte zur Kompilierungszeit festgelegt werden. Wenn Sie einen Reflektor verwenden, wird für MyMethod2 in BBB Folgendes angezeigt.

public override void MyMethod2()
{
    this.MyMethod("aaa");
}
chandmk
quelle
Ja, sie werden zur Kompilierungszeit behoben. Die Frage ist, warum der Compiler die Überladung der eigenen Klasse nicht berücksichtigt, sondern die Basisklasse verwendet.
Christian.K
Ich glaube nicht, dass das jemals in Frage gestellt wurde. Die Frage ist vielmehr, warum der Compiler diesen Teil der Methodengruppe und damit diesen Standardwert ausgewählt hat. Es reicht nicht aus zu sagen, dass "die Basisgruppe ausgewählt wird", da dies im Widerspruch zur ersten gezeigten Verwendung steht, bei der "bbb" gedruckt wird.
Marc Gravell
@MarcGravell Verzeih mir, aber war das der letzte Kommentar als Antwort auf meinen. Wenn ja, habe ich es nicht ganz verstanden :-)
Christian.K
@ Christian.K nein, es war zu user6130
Marc Gravell
Ich stimme zu, diese Beobachtung hätte eher ein Kommentar als eine Antwort sein sollen
chandmk
0

Stimmen Sie im Allgemeinen mit @Marc Gravell überein.

Ich möchte jedoch erwähnen, dass das Problem in der C ++ - Welt alt genug ist ( http://www.devx.com/tips/Tip/12737 ) und die Antwort wie folgt aussieht: "Im Gegensatz zu virtuellen Funktionen, die beim Ausführen behoben werden." Zeit werden Standardargumente statisch aufgelöst, dh zur kompilierten Zeit. " Daher wurde dieses Verhalten des C # -Compilers aufgrund seiner Konsistenz trotz seiner Unerwartetheit anscheinend eher bewusst akzeptiert.

Alexey Khoroshikh
quelle
0

So oder so braucht es eine Lösung

Ich würde es definitiv als Fehler betrachten, entweder weil die Ergebnisse falsch sind oder wenn die Ergebnisse erwartet werden, sollte der Compiler Sie nicht als "überschreiben" deklarieren lassen oder zumindest eine Warnung geben.

Ich würde Ihnen empfehlen, dies Microsoft.Connect zu melden

Aber ist es richtig oder falsch?

Lassen Sie uns jedoch zunächst die beiden Ansichten dazu analysieren, ob dies das erwartete Verhalten ist oder nicht.

Bedenken Sie, wir haben den folgenden Code:

void myfunc(int optional = 5){ /* Some code here*/ } //Function implementation
myfunc(); //Call using the default arguments

Es gibt zwei Möglichkeiten, dies zu implementieren:

  1. Diese optionalen Argumente werden wie überladene Funktionen behandelt, was Folgendes zur Folge hat:

    void myfunc(int optional){ /* Some code here*/ } //Function implementation
    void myfunc(){ myfunc(5); } //Default arguments implementation
    myfunc(); //Call using the default arguments
    
  2. Dass der Standardwert in den Aufrufer eingebettet ist, führt zu folgendem Code:

    void myfunc(int optional){ /* Some code here*/ } //Function implementation
    myfunc(5); //Call and embed default arguments
    

Es gibt viele Unterschiede zwischen den beiden Ansätzen, aber wir werden zunächst untersuchen, wie das .Net-Framework dies interpretiert.

  1. In .Net können Sie eine Methode nur mit einer Methode überschreiben, die dieselbe Anzahl von Argumenten enthält, aber Sie können nicht mit einer Methode überschreiben, die mehr Argumente enthält, selbst wenn alle optional sind (was dazu führen würde, dass ein Aufruf dieselbe Signatur wie die hat überschriebene Methode), sagen Sie zum Beispiel, Sie haben:

    class bassClass{ public virtual void someMethod()}
    class subClass :bassClass{ public override void someMethod()} //Legal
    //The following is illegal, although it would be called as someMethod();
    //class subClass:bassClass{ public override void someMethod(int optional = 5)} 
    
  2. Sie können eine Methode mit Standardargumenten mit einer anderen Methode ohne Argumente überladen (dies hat katastrophale Auswirkungen, wie ich gleich erläutern werde), sodass der folgende Code legal ist:

    void myfunc(int optional = 5){ /* Some code here*/ } //Function with default
    void myfunc(){ /* Some code here*/ } //No arguments
    myfunc(); //Call which one?, the one with no arguments!
    
  3. Bei der Verwendung von Reflection muss immer ein Standardwert angegeben werden.

All dies reicht aus, um zu beweisen, dass .Net die zweite Implementierung übernommen hat, sodass das Verhalten, das das OP sah, zumindest laut .Net richtig ist.

Probleme mit dem .Net-Ansatz

Es gibt jedoch echte Probleme mit dem .Net-Ansatz.

  1. Konsistenz

    • Wie beim Problem des OP beim Überschreiben des Standardwerts in einer geerbten Methode können die Ergebnisse unvorhersehbar sein

    • Wenn die ursprüngliche Implantation des Standardwerts geändert wird und die Anrufer nicht neu kompiliert werden müssen, werden möglicherweise Standardwerte angezeigt, die nicht mehr gültig sind

    • Für die Reflexion müssen Sie den Standardwert angeben, den der Anrufer nicht kennen muss
  2. Code brechen

    • Wenn wir eine Funktion mit Standardargumenten haben und letztere eine Funktion ohne Argumente hinzufügen, werden alle Aufrufe jetzt an die neue Funktion weitergeleitet, wodurch der gesamte vorhandene Code ohne Benachrichtigung oder Warnung beschädigt wird!

    • Ähnliches passiert, wenn wir die Funktion später ohne Argumente entfernen, werden alle Aufrufe automatisch mit den Standardargumenten an die Funktion weitergeleitet, wiederum ohne Benachrichtigung oder Warnung! obwohl dies möglicherweise nicht die Absicht des Programmierers ist

    • Darüber hinaus muss es keine reguläre Instanzmethode sein, eine Erweiterungsmethode führt dieselben Probleme aus, da eine Erweiterungsmethode ohne Parameter Vorrang vor einer Instanzmethode mit Standardparametern hat!

Zusammenfassung: BLEIBEN SIE VON OPTIONALEN ARGUMENTEN FERN UND VERWENDEN SIE STATT ÜBERLASTUNGEN (WIE DER .NET-RAHMEN SELBST TUT)

yoel halb
quelle