C # 4.0에서 오버로드 또는 선택적 매개 변수를 사용하여 메서드를 선언해야합니까?


94

Anders의 C # 4.0 및 C # 5.0 미리보기에 대한 이야기를 보고 있었는데 , C #에서 선택적 매개 변수를 사용할 수있을 때 모든 매개 변수를 지정할 필요가없는 메소드를 선언하는 데 권장되는 방법이 무엇인지 생각하게되었습니다.

예를 들어, FileStream클래스 와 같은 것에 는 논리적 '패밀리'로 나눌 수있는 약 15 개의 다른 생성자가 있습니다. 예를 들어 문자열에서 아래의 것, an IntPtr의 것, SafeFileHandle.

FileStream(string,FileMode);
FileStream(string,FileMode,FileAccess);
FileStream(string,FileMode,FileAccess,FileShare);
FileStream(string,FileMode,FileAccess,FileShare,int);
FileStream(string,FileMode,FileAccess,FileShare,int,bool);

이 유형의 패턴은 3 개의 생성자를 대신 사용하고 기본값으로 설정할 수있는 매개 변수에 대해 선택적 매개 변수를 사용하여 단순화 할 수있는 것 같습니다. 이렇게하면 생성자의 다른 패밀리가 더 뚜렷해집니다. [참고 :이 변경 사항은 BCL에서 만든, 나는 이러한 유형의 상황에 대해 가상으로 이야기하고 있습니다].

어떻게 생각해? C # 4.0부터는 밀접하게 관련된 생성자 및 메서드 그룹을 선택적 매개 변수가있는 단일 메서드로 만드는 것이 더 합리적일까요? 아니면 기존의 많은 오버로드 메커니즘을 고수해야 할 좋은 이유가 있습니까?

답변:


122

다음을 고려합니다.

  • 선택적 매개 변수를 지원하지 않는 언어에서 코드를 사용해야합니까? 그렇다면 과부하 포함을 고려하십시오.
  • 팀에 선택적 매개 변수를 강력하게 반대하는 구성원이 있습니까? (때때로 사건을 주장하는 것보다 마음에 들지 않는 결정으로 사는 것이 더 쉽습니다.)
  • 코드 빌드간에 기본값이 변경되지 않을 것이라고 확신합니까? 아니면 가능하다면 호출자가 괜찮을까요?

기본값이 어떻게 작동하는지 확인하지는 않았지만 기본값이 const필드에 대한 참조와 거의 동일하게 호출 코드에 구워 진다고 가정 합니다. 일반적으로 괜찮습니다. 기본값에 대한 변경은 어쨌든 상당히 중요하지만 고려해야 할 사항입니다.


21
실용주의에 대한 지혜에 +1 : 때로는 사건을 주장하는 것보다 마음에 들지 않는 결정을 내리고 사는 것이 더 쉽습니다.
legends2k 2014

13
@romkyns : 아니요, 오버로드의 효과는 포인트 3과 동일하지 않습니다. 오버로드가 기본값을 제공하는 경우 기본값 은 라이브러리 코드 내에 있습니다. 따라서 기본값을 변경하고 새 버전의 라이브러리를 제공하면 호출자가 재 컴파일하지 않고 새 기본값을 참조하십시오. 선택적 매개 변수의 경우 새 기본값을 "확인"하려면 다시 컴파일해야합니다. 많은 시간 그것은 중요한 차이 아니지만, 그것은 이다 구별.
Jon Skeet

안녕하세요 @ JonSkeet, 옵션 매개 변수와 함께 함수를 사용하고 어떤 메서드가 호출 될 오버로딩이있는 다른 함수를 모두 사용하는지 알고 싶습니다. 예를 들어 Add (int a, int b) 및 Add (int a, int b, int c = 0) 및 함수 호출은 다음과 같이 말합니다. Add (5,10); 오버로드 된 함수 또는 선택적 매개 변수 함수라고하는 메서드는 무엇입니까? 감사합니다 :)
SHEKHAR SHETE

@Shekshar : 시도해 보셨습니까? 자세한 내용은 사양을 읽으십시오. 그러나 기본적으로 타이 브레이커에서는 컴파일러가 선택적 매개 변수를 채울 필요가없는 메서드가 이깁니다.
Jon Skeet

@JonSkeet 방금 위와 함께 시도했습니다 ... 함수 오버로딩이 선택적 매개 변수보다 승리합니다. :)
SHEKHAR SHETE

19

메서드 오버로드가 일반적으로 다른 수의 인수로 동일한 작업을 수행하면 기본값이 사용됩니다.

메서드 오버로드가 매개 변수에 따라 다른 기능을 수행하면 오버로딩이 계속 사용됩니다.

VB6 일에 선택 사항을 사용했고 그 이후로 놓쳤으므로 C #에서 XML 주석 중복을 많이 줄일 수 있습니다.


11

저는 선택적 매개 변수와 함께 Delphi를 영원히 사용해 왔습니다. 대신 과부하를 사용하도록 전환했습니다.

더 많은 오버로드를 만들려고 할 때 선택적 매개 변수 형식과 항상 충돌하므로 어쨌든 비 옵션으로 변환해야합니다.

그리고 저는 일반적으로 하나의 슈퍼 메서드가 있고 나머지는 그 주위에 더 간단한 래퍼 라는 개념이 마음에 듭니다 .


1
나는 이것에 매우 동의하지만, 여러 (3+) 매개 변수를 취하는 메소드가 자연적으로 모두 "선택적"(기본값으로 대체 될 수 있음) 인 경우 많은 순열로 끝날 수 있다는 경고가 있습니다. 더 이상 이득이없는 메서드 서명의. 고려 Foo(A, B, C)가 필요 Foo(A), Foo(B), Foo(C), Foo(A, B), Foo(A, C), Foo(B, C).
Dan Lugg

7

4.0의 선택적 매개 변수 기능을 확실히 사용할 것입니다. 그것은 어리석은 것을 제거합니다 ...

public void M1( string foo, string bar )
{
   // do that thang
}

public void M1( string foo )
{
  M1( foo, "bar default" ); // I have always hated this line of code specifically
}

... 발신자가 볼 수있는 곳에 값을 넣습니다.

public void M1( string foo, string bar = "bar default" )
{
   // do that thang
}

훨씬 더 간단하고 오류 발생 가능성이 훨씬 적습니다. 나는 실제로 이것을 과부하 사례의 버그로 보았습니다 ...

public void M1( string foo )
{
   M2( foo, "bar default" );  // oops!  I meant M1!
}

나는 아직 4.0 컴파일러를 가지고 놀지 않았지만 컴파일러가 단순히 당신을 위해 과부하를 방출한다는 것을 알고 충격을받지 않을 것입니다.


6

선택적 매개 변수는 기본적으로 메서드 호출을 처리하는 컴파일러가 호출 사이트에 적절한 기본값을 삽입하도록 지시하는 메타 데이터입니다. 반대로, 오버로드는 컴파일러가 여러 메서드 중 하나를 선택할 수있는 수단을 제공하며, 그중 일부는 기본값을 제공 할 수 있습니다. 옵션 매개 변수를 지원하지 않는 언어로 작성된 코드에서 선택적 매개 변수를 지정하는 메소드를 호출하려는 경우 컴파일러는 "선택적"매개 변수를 지정해야하지만 선택적 매개 변수를 지정하지 않고 메소드를 호출하면 다음과 같습니다. 기본값과 동일한 매개 변수를 사용하여 호출하는 것과 동일하므로 이러한 메서드를 호출하는 언어에는 장애물이 없습니다.

호출 사이트에서 선택적 매개 변수 바인딩의 중요한 결과는 컴파일러에서 사용할 수있는 대상 코드의 버전에 따라 값이 할당된다는 것입니다. 어셈블리 에 기본값이 5 인 Foo메서드 Boo(int)가 있고 어셈블리 Bar에에 대한 호출이 포함 된 Foo.Boo()경우 컴파일러는이를 Foo.Boo(5). 기본값 어셈블리 (6)로 변경하고 경우 Foo다시 컴파일, Bar전화를 계속 Foo.Boo(5)이의 새로운 버전으로 다시 컴파일 될 때까지하지 않는 나 Foo. 따라서 변경 될 수있는 항목에 대해 선택적 매개 변수를 사용하지 않아야합니다.


Re : "따라서 변경 될 수있는 항목에 대해 선택적 매개 변수를 사용하지 않아야합니다." 변경 사항이 클라이언트 코드에 의해 눈에 띄지 않으면 문제가 될 수 있다는 데 동의합니다. 그러나 메서드 오버로드 내부에 기본값이 숨겨져있는 경우에도 동일한 문제가 발생합니다 void Foo(int value) … void Foo() { Foo(42); }. 외부에서 호출자는 어떤 기본값이 사용되는지, 언제 변경 될 수 있는지 알지 못합니다. 이를 위해 서면 문서를 모니터링해야합니다. 선택적 매개 변수의 기본값은 다음과 같이 볼 수 있습니다.
stakx-더 이상 기여하지 않습니다

@stakx : 매개 변수없는 오버로드가 매개 변수가있는 오버로드에 연결되는 경우 해당 매개 변수의 "기본값"값을 변경하고 오버로드 정의를 다시 컴파일 하면 호출 코드가 다시 컴파일되지 않더라도 사용하는 값이 변경됩니다 .
supercat

사실이지만 그것이 대안보다 더 문제가되지는 않습니다. 한 가지 경우 (메서드 오버로드), 호출 코드는 기본값에 대한 설명이 없습니다. 이것은 호출 코드가 선택 매개 변수와 그 의미에 대해 전혀 신경 쓰지 않는 경우 적절할 수 있습니다. 다른 경우 (기본값이있는 선택적 매개 변수), 이전에 컴파일 된 호출 코드는 기본값이 변경 될 때 영향을받지 않습니다. 이는 호출 코드가 실제로 매개 변수에 관심을 가질 때도 적절할 수 있습니다. 소스에서 생략하면 "현재 제안 된 기본값은 괜찮습니다"라고 말하는 것과 같습니다.
stakx - 더 이상 기여하지

내가 여기서 말하고자하는 요점은 (당신이 지적한 것처럼) 두 접근 방식에 결과가 있지만 본질적으로 유리하거나 불리하지 않다는 것입니다. 그것은 또한 호출 코드의 요구와 목표에 달려 있습니다. 그 POV에서 답변의 마지막 문장의 평결이 다소 경직된 것으로 나타났습니다.
stakx - 더 이상 기여하지

@stakx : "절대 사용하지 않음"보다는 "사용하지 마십시오"라고 말했습니다. X를 변경하는 것이 Y의 다음 재 컴파일이 Y의 동작을 변경한다는 것을 의미한다면, 빌드 시스템을 구성하여 X를 재 컴파일 할 때마다 Y도 재 컴파일하도록 구성해야하거나 (느려짐) 프로그래머가 변경 될 위험이 있습니다. X는 다음에 컴파일 될 때 Y를 깨뜨리는 방식으로, 완전히 관련이없는 이유로 Y가 변경 될 때만 나중에 그러한 손상을 발견합니다. 기본 매개 변수는 장점이 이러한 비용보다 클 때만 사용해야합니다.
supercat

4

선택적 인수 또는 오버로드를 사용해야하는지 여부가 논쟁의 여지가 있지만 가장 중요한 것은 각각 대체 할 수없는 고유 한 영역이 있다는 것입니다.

선택적 인수는 명명 된 인수와 함께 사용할 때 COM 호출의 모든 옵션이 포함 된 긴 인수 목록과 결합 할 때 매우 유용합니다.

오버로드는 메소드가 여러 다른 인수 유형 (예제 중 하나)에서 작동 할 수 있고 예를 들어 내부적으로 캐스팅 할 때 매우 유용합니다. 의미가있는 데이터 유형 (기존 오버로드에 의해 허용됨)을 제공하기 만하면됩니다. 선택적 인수로는 이길 수 없습니다.


3

나는 기본값이 메소드에 더 가까운 것을 유지하기 때문에 선택적 매개 변수를 기대하고 있습니다. 따라서 "expanded"메서드를 호출하는 오버로드에 대한 수십 줄 대신 메서드를 한 번만 정의하면 메서드 서명에서 선택적 매개 변수의 기본값을 확인할 수 있습니다. 차라리 다음을보고 싶습니다.

public Rectangle (Point start = Point.Zero, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

대신 :

public Rectangle (Point start, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

public Rectangle (int width, int height) :
    this (Point.Zero, width, height)
{
}

분명히이 예제는 매우 간단하지만 5 개의 과부하가있는 OP의 경우 상황이 매우 빠르게 혼잡해질 수 있습니다.


7
선택적 매개 변수가 마지막이어야한다고 들었습니다. 그렇지 않습니까?
Ilya Ryzhenkov

디자인에 따라 다릅니다. 아마도 'start'인수는 그렇지 않은 경우를 제외하고는 일반적으로 중요합니다. 아마도 당신은 다른 곳에 동일한 서명을 가지고있을 것입니다. 인위적인 예를 들어, public Rectangle (int width, int height, Point innerSquareStart, Point innerSquareEnd) {}
Robert P

13
그들이 이야기에서 말한 것에서 선택적 매개 변수는 필수 매개 변수 뒤에 있어야합니다.
Greg Beech

3

선택적 매개 변수에서 내가 가장 좋아하는 측면 중 하나는 메소드 정의로 이동하지 않아도 매개 변수를 제공하지 않으면 매개 변수에 어떤 일이 발생하는지 볼 수 있다는 것입니다. Visual Studio는 단순히 기본값을 표시합니다. 메서드 이름을 입력 할 때 매개 변수 . 오버로드 메서드를 사용하면 설명서를 읽거나 (사용 가능한 경우) 메서드의 정의 (사용 가능한 경우) 및 오버로드가 래핑하는 메서드로 직접 이동하는 데 어려움을 겪습니다.

특히 : 과부하의 양에 따라 문서화 작업이 빠르게 증가 할 수 있으며 기존 과부하에서 이미 존재하는 주석을 복사하게 될 것입니다. 이것은 어떤 가치도 생성하지 않고 DRY 원칙을 위반하기 때문에 매우 성가신 일 입니다. 반면에 선택적 매개 변수를 사용하면 모든 매개 변수가 문서화되어 입력하는 동안 그 의미와 기본값 을 볼 수있는 정확히 한 곳 이 있습니다 .

마지막으로 API의 소비자 인 경우 구현 세부 정보를 검사 할 수있는 옵션도 없을 수 있으므로 (소스 코드가없는 경우) 오버로드 된 슈퍼 메서드를 확인할 기회가 없습니다. 포장하고 있습니다. 따라서 문서를 읽고 모든 기본값이 거기에 나열되기를 바라고 있지만 항상 그런 것은 아닙니다.

물론 이것은 모든 측면을 다루는 답변은 아니지만 지금까지 다루지 않은 답변을 추가한다고 생각합니다.


1

API를 처음부터 모델링 할 수있는 개념적으로 동일한 두 가지 방법이 있지만 (아마도?), 안타깝게도 기존 클라이언트에 대한 런타임 이전 버전과의 호환성을 고려해야 할 때 약간의 미묘한 차이가 있습니다. 제 동료 (Brent에게 감사합니다!)는 저에게이 멋진 게시물 인 선택적 인수를 사용한 버전 관리 문제를 지적했습니다 . 그것의 일부 인용 :

선택적 매개 변수가 처음에 C # 4에 도입 된 이유는 COM interop을 지원하기 위해서입니다. 그게 다야. 그리고 이제 우리는이 사실의 완전한 의미에 대해 배우고 있습니다. 선택적 매개 변수가있는 메서드가있는 경우 컴파일 시간에 주요 변경이 발생할 수 있으므로 추가 선택적 매개 변수를 사용하여 오버로드를 추가 할 수 없습니다. 그리고 이것은 항상 런타임 주요 변경 사항이므로 기존 오버로드를 제거 할 수 없습니다. 인터페이스처럼 취급해야합니다. 이 경우 유일한 방법은 새 이름으로 새 메서드를 작성하는 것입니다. 따라서 API에서 선택적 인수를 사용하려는 경우이 점에 유의하십시오.


1

선택적 매개 변수의 한 가지주의 사항은 리팩터링으로 인해 의도하지 않은 결과가 발생하는 버전 관리입니다. 예 :

초기 코드

public string HandleError(string message, bool silent=true, bool isCritical=true)
{
  ...
}

이것이 위 메소드의 많은 호출자 중 하나라고 가정합니다.

HandleError("Disk is full", false);

여기서 이벤트는 조용하지 않으며 중요한 것으로 취급됩니다.

이제 리팩터링 후 모든 오류가 사용자에게 프롬프트를 표시하므로 더 이상 자동 플래그가 필요하지 않다고 가정 해 보겠습니다. 그래서 우리는 그것을 제거합니다.

리팩터링 후

이전 호출은 여전히 ​​컴파일되고 변경되지 않은 상태로 리팩터링을 통과한다고 가정 해 보겠습니다.

public string HandleError(string message, /*bool silent=true,*/ bool isCritical=true)
{
  ...
}

...

// Some other distant code file:
HandleError("Disk is full", false);

이제 false의도하지 않은 효과가 발생하고 이벤트가 더 이상 중요한 것으로 간주되지 않습니다.

이것은 컴파일 또는 런타임 오류가 없기 때문에 미묘한 결함이 발생할 수 있습니다 ( this 또는 this 와 같은 다른 선택 사항에 대한주의 사항과는 달리 ).

이 같은 문제에는 여러 형태가 있습니다. 다른 양식이 여기 에 설명되어 있습니다 .

메서드를 호출 할 때 명명 된 매개 변수를 엄격하게 사용하면 다음과 같은 문제를 피할 수 HandleError("Disk is full", silent:false)있습니다. 그러나 다른 모든 개발자 (또는 공용 API 사용자)가 그렇게 할 것이라고 가정하는 것은 실용적이지 않을 수 있습니다.

이러한 이유로 저는 다른 설득력있는 고려 사항이없는 한 공용 API (또는 널리 사용되는 경우 공용 메서드)에서 선택적 매개 변수를 사용하지 않습니다.


0

선택적 매개 변수, 메서드 오버로드에는 모두 장점이나 단점이 있습니다. 둘 중 선택하는 선호도에 따라 다릅니다.

선택적 매개 변수 : .Net 4.0에서만 사용할 수 있습니다. 선택적 매개 변수는 코드 크기를 줄입니다. out 및 ref 매개 변수를 정의 할 수 없습니다.

오버로드 된 메서드 : Out 및 ref 매개 변수를 정의 할 수 있습니다. 코드 크기는 증가하지만 오버로드 된 메서드는 이해하기 쉽습니다.


0

많은 경우 선택적 매개 변수가 실행을 전환하는 데 사용됩니다. 예를 들면 :

decimal GetPrice(string productName, decimal discountPercentage = 0)
{

    decimal basePrice = CalculateBasePrice(productName);

    if (discountPercentage > 0)
        return basePrice * (1 - discountPercentage / 100);
    else
        return basePrice;
}

여기서 할인 매개 변수는 if-then-else 문을 제공하는 데 사용됩니다. 인식되지 않은 다형성이 있으며 if-then-else 문으로 구현되었습니다. 이러한 경우 두 제어 흐름을 두 개의 독립적 인 방법으로 분할하는 것이 훨씬 좋습니다.

decimal GetPrice(string productName)
{
    decimal basePrice = CalculateBasePrice(productName);
    return basePrice;
}

decimal GetPrice(string productName, decimal discountPercentage)
{

    if (discountPercentage <= 0)
        throw new ArgumentException();

    decimal basePrice = GetPrice(productName);

    decimal discountedPrice = basePrice * (1 - discountPercentage / 100);

    return discountedPrice;

}

이런 식으로 우리는 수업이 할인없이 전화를받지 못하도록 보호했습니다. 그 호출은 발신자가 할인이 있다고 생각하지만 실제로는 할인이 전혀 없음을 의미합니다. 이러한 오해는 쉽게 버그를 유발할 수 있습니다.

이와 같은 경우에는 선택적 매개 변수가없는 것을 선호하지만 호출자가 현재 상황에 맞는 실행 시나리오를 명시 적으로 선택하도록 강제합니다.

상황은 널이 될 수있는 매개 변수를 갖는 것과 매우 유사합니다. 구현이 if (x == null).

다음 링크에서 자세한 분석을 찾을 수 있습니다. 선택적 매개 변수 방지Null 매개 변수 방지


0

선택 사항 대신 오버로드를 사용할 때 쉽게 추가하려면 :

함께 의미가있는 여러 매개 변수가있을 때마다 옵션을 도입하지 마십시오.

또는 더 일반적으로 메서드 서명이 의미가없는 사용 패턴을 활성화 할 때마다 가능한 호출의 순열 수를 제한합니다. 예를 들어, 옵션 대신 오버로드를 사용하여 (이 규칙은 동일한 데이터 유형의 여러 매개 변수가있는 경우에도 적용됩니다. 여기서는 팩토리 메소드 또는 사용자 정의 데이터 유형과 같은 장치가 도움이 될 수 있습니다).

예:

enum Match {
    Regex,
    Wildcard,
    ContainsString,
}

// Don't: This way, Enumerate() can be called in a way
//         which does not make sense:
IEnumerable<string> Enumerate(string searchPattern = null,
                              Match match = Match.Regex,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);

// Better: Provide only overloads which cannot be mis-used:
IEnumerable<string> Enumerate(SearchOption searchOption = SearchOption.TopDirectoryOnly);
IEnumerable<string> Enumerate(string searchPattern, Match match,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.