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();
}
}
Antworten:
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:
Eine Methode in einer Klasse kann eine oder zwei der folgenden Aktionen ausführen:
Es kann nicht beides tun, da eine abstrakte Methode nur die erstere tut.
Innerhalb
BBB
des AufrufsMyMethod()
ruft eine Methode definiert inAAA
.Da es eine Überschreibung gibt
BBB
, führt der Aufruf dieser Methode dazu, dass eine ImplementierungBBB
aufgerufen wird.Die Definition in
AAA
informiert den aufrufenden Code über zwei Dinge (nun, einige andere, die hier keine Rolle spielen).void MyMethod(string)
."aaa"
Wenn beim Kompilieren des Codes des FormularsMyMethod()
keine MethodenübereinstimmungMyMethod()
gefunden werden kann, können Sie ihn durch einen Aufruf von "MyMethod" ("aaa") ersetzen.Das macht der Aufruf
BBB
also so: Der Compiler sieht einen Aufruf vonMyMethod()
, findet keine MethodeMyMethod()
, findet aber eine MethodeMyMethod(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ändertMyMethod("aaa")
.Von innen
BBB
,AAA
ist der Ort , an dem alsAAA
‚s Methoden definiert werden, auch außer Kraft gesetzt , wenn inBBB
, 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
BBB
dann programmierenMyMethod(string)
würde, egal ob ich anrufe oder überschreibe , würde ich das als "AAA
Sachen machen" betrachten - es istBBB
"AAA
Sachen machen", aber es macht trotzdemAAA
Sachen. Unabhängig davon, ob ich anrufe oder überschreibe, werde ich mir der Tatsache bewusst sein, dass esAAA
so definiert warMyMethod(string)
.Wenn ich den verwendeten Code aufrufen
BBB
würde, würde ich daran denken, "BBB
Sachen zu verwenden". Ich bin mir möglicherweise nicht sehr bewusst, in was ursprünglich definiert wurdeAAA
, und ich würde dies möglicherweise nur als Implementierungsdetail betrachten (wenn ich nicht auch dieAAA
Schnittstelle 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.
quelle
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:
(und tatsächlich lässt §7.4
override
Methoden 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
override
nicht sind berücksichtigt.In §7.5.1.1 heißt es dann jedoch:
und dann erklärt §7.5.1.2, wie die Werte zum Zeitpunkt des Aufrufs ausgewertet werden:
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) ).quelle
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
this
Prä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:
Und Abschnitt 7.5.1.2:
Der "entsprechende optionale Parameter" ist das Bit, das 7.5.2 mit 7.5.1.1 verbindet.
Für beide
M()
undthis.M()
, dass Parameterliste der in sein sollteDerived
als statischer Typ des Empfängers istDerived
, die Tat kann man sagen , dass die Compiler behandelt , dass die Parameterliste weiter oben in der Zusammenstellung, als ob Sie die Parameter machen obligatorisch inDerived.M()
, sowohl von Die Aufrufe schlagen fehl - für denM()
Aufruf muss der Parameter einen Standardwert habenDerived
, dieser wird jedoch ignoriert!Tatsächlich wird es noch schlimmer: wenn Sie einen Standardwert für die Parameter in bereitstellen ,
Derived
aber es zwingend machenBase
, die AnrufendeM()
bis Verwendungnull
als Argument Wert. Wenn nichts anderes, würde ich sagen, dass dies ein Fehler ist: Diesernull
Wert kann nicht von irgendwoher kommen, wo er gültig ist. (null
Dies liegt daran, dass dies der Standardwert desstring
Typs 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(); } }
quelle
this.
und die erforderlichen / optionalen Kuriositäten, die Sie hervorheben, alle ernsthaft seltsam und sehen sehr kaputt aus.Hast du es versucht:
public override void MyMethod2() { this.MyMethod(); }
Sie weisen Ihr Programm also tatsächlich an, die Überschreibungsmethode zu verwenden.
quelle
this
ist impliziert. Somit ist Ihre Antwort überflüssig.base
.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.
quelle
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
this
Schlü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
this
Schlü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.
================================================== ========================
quelle
this
angedeutet wird (nie diese Marotte ausmacht).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).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"); }
quelle
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.
quelle
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:
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
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.
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)}
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!
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.
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
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)
quelle