재정의 된 메서드에 대한 C # 선택적 매개 변수
.NET Framework에서 메서드를 재정의 할 때 선택적 매개 변수에 문제가있는 것처럼 보입니다. 아래 코드의 출력은 "bbb" "aaa"입니다. 그러나 내가 기대하는 출력은 : "bbb" "bbb". 이것에 대한 해결책이 있습니까? 메서드 오버로딩으로 해결할 수 있다는 것을 알고 있지만 그 이유가 궁금합니다. 또한 코드는 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();
}
}
여기서 주목할 가치가있는 한 가지는 재정의 된 버전이 매번 호출된다는 것입니다. 재정의를 다음으로 변경합니다.
public override void MyMethod(string s = "bbb")
{
Console.Write("derived: ");
base.MyMethod(s);
}
출력은 다음과 같습니다.
derived: bbb
derived: aaa
클래스의 메서드는 다음 중 하나 또는 두 가지를 수행 할 수 있습니다.
- 다른 코드가 호출 할 인터페이스를 정의합니다.
- 호출시 실행할 구현을 정의합니다.
추상적 인 방법은 전자 만 수행하므로 둘 다 수행 할 수는 없습니다.
내에서 BBB
호출 MyMethod()
하는 방법 호출 정의 에를 AAA
.
에 재정의가 있으므로 BBB
해당 메서드를 호출하면 구현 BBB
이 호출됩니다.
이제의 정의 AAA
는 호출 코드에 두 가지 (음, 여기서 중요하지 않은 몇 가지 다른 것) 를 알려줍니다.
- 서명
void MyMethod(string)
. - (지원하는 언어의 경우) 단일 매개 변수의 기본값은
"aaa"
이므로MyMethod()
일치하는 메소드를MyMethod()
찾을 수 없는 경우 양식의 코드를 컴파일 할 때 `MyMethod ( "aaa") 호출로 바꿀 수 있습니다.
그래서 그것은 호출 BBB
이하는 일입니다 : 컴파일러는에 대한 호출을보고 MyMethod()
, 메소드를 MyMethod()
찾지 못하지만 메소드를 찾습니다 MyMethod(string)
. 또한 정의 된 위치에 기본값 "aaa"가 있음을 확인하므로 컴파일 타임에이 값을에 대한 호출로 변경합니다 MyMethod("aaa")
.
내에서 BBB
, AAA
장소 고려 AAA
에서 재정의하는 경우에도,의 방법이 정의 BBB
가 그래서, 수 오버라이드 (override)합니다.
런타임에 MyMethod(string)
"aaa"인수와 함께 호출됩니다. 재정의 된 양식이 있기 때문에 호출되는 양식이지만 "bbb"로 호출되지 않습니다. 해당 값은 런타임 구현과 관련이 없지만 컴파일 타임 정의와 관련이 있기 때문입니다.
추가하면 this.
검사되는 정의가 변경되므로 호출에 사용되는 인수가 변경됩니다.
편집 : 이것이 나에게 더 직관적 인 이유.
개인적으로, 직관적 인 것에 대해 이야기하고 있기 때문에 개인적 일 수밖에 없기 때문에 다음과 같은 이유로 더 직관적이라고 생각합니다.
을 BBB
호출하든 재정의하든 코딩 을 했다면 MyMethod(string)
"일을하는 AAA
것" 이라고 생각할 것입니다. "일을하는 것"을 BBB
취하는 AAA
것이지만 AAA
모든 일을 똑같이합니다. 따라서 호출하든 재정의하든, 그것이 AAA
정의 된 이라는 사실을 알게 될 것 MyMethod(string)
입니다.
을 사용하는 코드를 호출한다면 BBB
"사용"을 생각할 것입니다 BBB
. 에서 원래 정의 된 것이 무엇인지 잘 알지 못할 수 있으며 AAA
, 아마도 이것을 구현 세부 사항으로 생각할 것입니다 ( AAA
근처 의 인터페이스 도 사용하지 않았다면 ).
컴파일러의 동작은 내 직감과 일치하므로 처음 질문을 읽을 때 Mono에 버그가있는 것처럼 보였습니다. 고려할 때 어느 쪽이 다른 것보다 지정된 행동을 어떻게 더 잘 수행하는지 알 수 없습니다.
그 문제에 관해서는 개인적 수준에 머무르면서 추상, 가상 또는 재정의 된 메서드와 함께 선택적 매개 변수를 사용하지 않았으며 다른 사람을 재정의하면 해당 매개 변수와 일치시킬 것입니다.
다음을 호출하여 명확하게 할 수 있습니다.
this.MyMethod();
(에서 MyMethod2()
)
버그인지 여부는 까다 롭습니다. 그래도 일관성이 없어 보입니다. ReSharper에서 그 도움이된다면 당신은 단순히 재정의에서 기본값을 변경해야하지 경고, 물론 p는, ReSharper에서이 도 를 사용해서 알려줍니다 this.
중복 및 이벤트 동작을 변경하는 ... 당신을 위해 그것을 제거하는 - 그래서 ReSharper에서도 완벽하지 않습니다.
컴파일러 버그로 간주 될 수있는 것 같습니다 . 확실하게하려면 정말 조심스럽게 살펴 봐야 겠어요 ... 에릭이 필요할 때 어디 있나요?
편집하다:
여기서 핵심은 언어 사양입니다. §7.5.3을 살펴 보겠습니다.
예를 들어 메서드 호출 후보 집합에는 재정의 (§7.4)로 표시된 메서드가 포함되지 않으며 파생 클래스의 메서드를 적용 할 수있는 경우 기본 클래스의 메서드는 후보가 아닙니다 (§7.6.5.1).
(실제로 §7.4 override
는 고려에서 방법을 명확하게 생략 합니다)
여기에 약간의 충돌이 있습니다 .... 파생 된 클래스에 적용 가능한 메서드가있는 경우 기본 메서드가 사용되지 않는다는 의미입니다.이 경우 파생 된 메서드로 연결되지만 동시에 표시된 메서드 override
는 깊이 생각한.
그러나 §7.5.1.1은 다음과 같이 명시합니다.
클래스에 정의 된 가상 메서드 및 인덱서의 경우 매개 변수 목록은 수신자의 정적 유형에서 시작하여 기본 클래스를 검색하는 함수 멤버의 가장 구체적인 선언 또는 재정의에서 선택됩니다.
그리고 §7.5.1.2는 호출시 값이 평가되는 방법을 설명합니다.
함수 멤버 호출 (§7.5.4)의 런타임 처리 중에 인수 목록의 식 또는 변수 참조는 다음과 같이 왼쪽에서 오른쪽으로 순서대로 평가됩니다.
...(한조각)...
해당 선택적 매개 변수가있는 함수 멤버에서 인수가 생략되면 함수 멤버 선언의 기본 인수가 암시 적으로 전달됩니다. 이들은 항상 일정하므로 평가는 나머지 인수의 평가 순서에 영향을주지 않습니다.
이것은 이전에 §7.5.1.1에서 가장 구체적인 선언 또는 재정의 에서 온 것으로 정의 된 인수 목록을보고 있음을 명시 적으로 강조합니다 . 이것이 §7.5.1.2에 언급 된 "메서드 선언"이라는 것이 합리적으로 보입니다. 따라서 전달 된 값은 가장 많이 파생 된 것부터 정적 유형까지 여야합니다.
이것은 다음을 제안합니다. csc에는 버그가 있으며 기본 메서드 선언 (§7.6.8을 통해을 통해 또는 기본 형식으로 캐스팅)을 보는 것으로 제한되지 않는 한 파생 버전 ( "bbb bbb")을 base.
사용해야합니다. ).
이것은 나에게 버그처럼 보입니다. 나는 그것이 생각 입니다 잘 지정, 당신은 명시 적으로 메서드 호출 것처럼 같은 방식으로 행동해야한다는 this
접두사.
단일 가상 메서드 만 사용하도록 예제를 단순화하고 호출되는 구현과 매개 변수 값을 모두 보여줍니다.
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();
}
}
따라서 우리가 걱정해야 할 것은 RunTests 내의 세 가지 호출입니다. 처음 두 호출에 대한 스펙의 중요한 부분은 7.5.1.1 섹션으로, 해당 매개 변수를 찾을 때 사용할 매개 변수 목록에 대해 설명합니다.
클래스에 정의 된 가상 메서드 및 인덱서의 경우 매개 변수 목록은 수신자의 정적 유형에서 시작하여 기본 클래스를 검색하는 함수 멤버의 가장 구체적인 선언 또는 재정의에서 선택됩니다.
그리고 섹션 7.5.1.2 :
해당 선택적 매개 변수가있는 함수 멤버에서 인수가 생략되면 함수 멤버 선언의 기본 인수가 암시 적으로 전달됩니다.
"해당 선택적 매개 변수"는 7.5.2와 7.5.1.1을 연결하는 비트입니다.
모두 들어 M()
및 this.M()
매개 변수 목록에서 하나가 될 것을, Derived
수신기의 정적 형식이기 때문에 Derived
, 실제로, 당신은 말할 수 컴파일러 취급하는 매개 변수 목록 이전 컴파일에서, 당신은 매개 변수를 만드는 경우와 같이 의무적 인을 Derived.M()
, 모두 의 호출이 실패합니다. 따라서 M()
호출 은 매개 변수가에서 기본값을 가져야 Derived
하지만 무시합니다!
실제로 더 나빠집니다. 매개 변수에 대한 기본값을 제공 Derived
하지만에서 필수로 설정 Base
하면 호출 M()
이 결국 null
인수 값으로 사용 됩니다. 다른 것이 없다면 그것이 버그라는 것을 증명한다고 말하고 싶습니다. 그 null
값은 유효한 곳에서 올 수 없습니다. (그건 null
그의 기본 가치에 의한 string
, 그것은 언제나 매개 변수 유형의 기본값을 사용하는 타입입니다.)
사양의 섹션 7.6.8은 base.M ()을 다룹니다. 이는 비가 상 동작 뿐 아니라 표현식이 다음과 같이 간주됩니다 ((Base) this).M()
. 따라서 유효한 매개 변수 목록을 결정하는 데 사용되는 기본 방법은 전적으로 정확합니다. 이는 마지막 줄이 정확하다는 것을 의미합니다.
아무데도 지정되지 않은 값이 사용되는 위에서 설명한 정말 이상한 버그를보고자하는 사람을 위해 일을 더 쉽게하기 위해 :
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();
}
}
시도해 보셨습니까?
public override void MyMethod2()
{
this.MyMethod();
}
따라서 실제로 프로그램에 재정의 된 메서드를 사용하도록 지시합니다.
동작은 확실히 매우 이상합니다. 실제로 컴파일러의 버그인지는 확실하지 않지만 그럴 수도 있습니다.
어젯밤 캠퍼스에 상당한 양의 눈이 내렸고 시애틀은 눈을 잘 다루지 못합니다. 내 버스가 오늘 아침에 운행되지 않으므로 C # 4, C # 5 및 Roslyn이이 사건에 대해 말한 것과 동의하지 않는지 비교하기 위해 사무실에 들어갈 수 없습니다. 이번 주 후반에 사무실로 돌아와 적절한 디버깅 도구를 사용할 수있게되면 분석을 게시하려고합니다.
이것은 모호성 때문일 수 있으며 컴파일러가 기본 / 수퍼 클래스에 우선 순위를 부여하고 있습니다. this
키워드에 대한 참조를 추가하여 클래스 BBB의 코드에 대한 아래 변경 사항 은 출력 '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
}
}
이것이 의미하는 것 중 하나는 가장 좋은 방법this
으로 현재 클래스 인스턴스에서 속성이나 메서드를 호출 할 때마다 항상 키워드 를 사용해야한다는 것 입니다.
기본 및 자식 메서드의이 모호함이 컴파일러 경고 (오류가 아닌 경우)를 발생시키지 않으면 걱정할 것이지만, 그렇다면 그것은 보이지 않는 것 같습니다.
================================================ ================
편집 : 아래 링크에서 발췌 한 샘플을 고려하십시오.
함정 : 선택적 매개 변수 값은 컴파일 타임입니다. 선택적 매개 변수를 사용할 때 명심해야 할 것이 한 가지 있습니다. 이 한 가지를 염두에두면 사용과 관련된 잠재적 인 함정을 잘 이해하고 피할 수 있습니다. 한 가지는 이것이다 : 선택적 매개 변수는 컴파일 타임, 구문 적 설탕입니다!
함정 : 상속 및 인터페이스 구현의 기본 매개 변수주의
이제 두 번째 잠재적 인 함정은 상속 및 인터페이스 구현과 관련이 있습니다. 퍼즐로 설명하겠습니다.
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: }
What happens? Well, even though the object in each case is SubTag whose tag is “SubTag”, you will get:
1: SubTag 2: BaseTag 3: ITag
But remember to make sure you:
Do not insert new default parameters in the middle of an existing set of default parameters, this may cause unpredictable behavior that may not necessarily throw a syntax error – add to end of list or create new method. Be extremely careful how you use default parameters in inheritance hierarchies and interfaces – choose the most appropriate level to add the defaults based on expected usage.
==========================================================================
This I think is because these default values are fixed at the compile time. If you use reflector you will see the following for MyMethod2 in BBB.
public override void MyMethod2()
{
this.MyMethod("aaa");
}
Agree in general with @Marc Gravell.
However, I'd like to mention that the issue is old enough in C++ world (http://www.devx.com/tips/Tip/12737), and the answer looks like "unlike virtual functions, which are resolved at run time, default arguments are resolved statically, that is, at compiled time." So this C# compiler behavior had rather been accepted deliberately due to consistency, despite its unexpectedness, it seems.
Either Way It Needs A Fix
I would definitely regard it as a bug, either because the results is wrong or if the results are expected then the compiler should not let you declare it as "override", or at least provide a warning.
I would recommend you to report this to Microsoft.Connect
But Is It Right Or Wrong?
However regarding whether this is the expected behavior or not, let us first analyze the two views on it.
consider we have the following code:
void myfunc(int optional = 5){ /* Some code here*/ } //Function implementation
myfunc(); //Call using the default arguments
There are two ways to implement it:
That optional arguments are treated like overloaded functions, resulting in the following:
void myfunc(int optional){ /* Some code here*/ } //Function implementation void myfunc(){ myfunc(5); } //Default arguments implementation myfunc(); //Call using the default arguments
That the default value is embedded in the caller, thus resulting in the following code:
void myfunc(int optional){ /* Some code here*/ } //Function implementation myfunc(5); //Call and embed default arguments
There are many differences between the two approaches, but we will first take a look on how the .Net framework interprets it.
In .Net you can only override a method with a method that contains the same number of arguments, but you cannot override with a method containing more arguments, even if they are all optional (which would result in a call haveing the same signature as the overridden method), say for example you have:
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)}
You can overload a method with default arguments with another method with no arguments, (this has disastrous implications as I will discuss in a moments), so the folloing code is legal:
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!
when using reflection one must always provide a default value.
All of which are enough to prove that .Net took the second implementation, so the behavior that the OP saw is right, at least according to .Net.
Problems With the .Net Approach
However there are real problems with the .Net approach.
Consistency
As in the OP's problem when overriding the default value in an inherited method, then results might be unpredictable
When the original implantation of the default value is changed, and since the callers don't have to get recompiled, we might end up with default values that are no longer valid
- Reflection requires you to provide the default value, which the caller doesn't have to know
Breaking code
When we have a function with default arguments and latter we add a function with no arguments, all calls will now route to the new function, thus breaking all existing code, without any notification or warning!
Similar will happen, if we later take away the function with no arguments, then all calls will automatically route to the function with the default arguments, again with no notification or warning! although this might not be the intention of the programmer
Furthermore it does not have to be regular instance method, an extension method will do the same problems, since an extension method with no parameters will take precedence over an instance method with default parameters!
Summary: STAY AWAY FROM OPTIONAL ARGUMENTS, AND USE INSTEAD OVERLOADS (AS THE .NET FRAMEWORK ITSELF DOES)
참고URL : https://stackoverflow.com/questions/8909811/c-sharp-optional-parameters-on-overridden-methods
'program story' 카테고리의 다른 글
정수를 정수로 변환하는 방법? (0) | 2020.10.30 |
---|---|
node.js의 fs.createReadStream 대 fs.readFile의 장단점은 무엇입니까? (0) | 2020.10.30 |
ASP.NET Core (.NET Core)와 ASP.NET Core (.NET Framework)의 차이점 (0) | 2020.10.30 |
jQuery 표시 / 숨기기가 visible : hidden 대신 display : none을 사용하는 이유는 무엇입니까? (0) | 2020.10.30 |
Scala에서 유형 지정의 목적은 무엇입니까? (0) | 2020.10.30 |