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

StackOverflow https://stackoverflow.com/questions/251868

문제

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

예를 들어 FileStream 클래스에는 논리적 '패밀리'로 나눌 수 있는 약 15개의 서로 다른 생성자가 있습니다.아래 문자열의 것, 문자열의 것 IntPtr 그리고 a에서 온 것 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);

이 유형의 패턴은 대신 세 개의 생성자를 사용하고 기본값으로 설정할 수 있는 생성자에 대해 선택적 매개 변수를 사용하여 여러 생성자 계열을 더욱 뚜렷하게 만들어 단순화할 수 있는 것 같습니다. [참고:나는 이 변경 사항이 BCL에서 이루어지지 않을 것이라는 것을 알고 있습니다. 나는 이러한 유형의 상황에 대해 가정적으로 이야기하고 있습니다.]

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

도움이 되었습니까?

해결책

다음을 고려할 것입니다.

  • 선택적 매개 변수를 지원하지 않는 언어에서 코드를 사용해야합니까? 그렇다면 과부하를 포함시키는 것을 고려하십시오.
  • 선택한 매개 변수에 대해 폭력적으로 반대하는 팀원이 있습니까? (때로는 사건을 논쟁하는 것보다 마음에 들지 않는 결정으로 사는 것이 더 쉽습니다.)
  • 귀하의 기본값이 코드 빌드간에 변경되지 않을 것이라고 확신하십니까?

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

다른 팁

메소드 과부하가 일반적으로 다른 수의 인수로 동일한 것을 수행하면 기본값이 사용됩니다.

메소드 오버로드가 매개 변수를 기반으로 함수를 다르게 수행하면 오버로드가 계속 사용됩니다.

VB6 일에 옵션을 사용한 후 이후를 놓쳤습니다. C#에서 많은 XML 주석 복제가 줄어 듭니다.

선택적인 매개 변수와 함께 Delphi를 영원히 사용했습니다. 대신 오버로드를 사용하여 전환했습니다.

더 많은 오버로드를 만들면 선택적 매개 변수 양식으로 변하지 않을 것입니다. 그러면 어쨌든 비 옵션으로 변환해야합니다.

그리고 나는 일반적으로 하나가 있다는 개념을 좋아합니다 감독자 방법과 나머지는 그 주위에 더 간단한 포장지입니다.

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 Complier와 함께 연주하지 않았지만, Complier가 단순히 과부하를 방출한다는 사실에 충격을받지 않을 것입니다.

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

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

기본값이 메소드에 더 가깝게 유지되므로 선택적 매개 변수를 기대합니다. 따라서 "확장 된"메소드를 호출하는 오버로드의 수십 줄 대신 메소드를 한 번만 정의하면 메소드 서명에서 선택한 매개 변수 기본값을 확인할 수 있습니다. 차라리보고 싶습니다 :

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의 경우에 상황이 빠르게 혼잡해질 수 있습니다.

선택적 인수 또는 과부하를 사용해야하는지 여부는 논쟁 할 수 있지만 가장 중요한 것은 각각 대체 할 수없는 자체 영역이 있습니다.

옵션 인수는 명명 된 인수와 함께 사용될 때 COM 호출의 장기적인리스트와 결합 될 때 매우 유용합니다.

오버로드는 메소드가 다양한 인수 유형 (예 중 하나)에서 작동 할 수 있고 예를 들어 내부적으로 캐스팅을 할 때 매우 유용합니다. 당신은 합리적 인 데이터 유형 (기존 오버로드로 받아 들여지는 데이터 유형)으로 공급합니다. 선택적 인수로 그것을 이길 수 없습니다.

선택적 매개 변수 중 내가 가장 좋아하는 측면 중 하나는 메소드 정의에 가지 않고도 제공하지 않으면 매개 변수에 어떤 일이 발생하는지 알 수 있다는 것입니다. Visual Studio는 단순히 기본값을 보여줍니다 메소드 이름을 입력 할 때 매개 변수의 경우 오버로드 메소드를 사용하면 문서를 읽거나 (사용 가능한 경우) 메소드의 정의 (사용 가능한 경우)와 오버로드가 랩하는 메소드를 직접 탐색하는 데 걸립니다.

특히 : 문서 노력은 과부하의 양에 따라 빠르게 증가 할 수 있으며, 기존 과부하에서 이미 기존 주석을 복사하게 될 것입니다. 이것은 어떤 가치도 생성하지 않고 깨지기 때문에 상당히 짜증입니다. 건조 원리). 반면에 선택적 매개 변수와 함께 정확히 한 곳 모든 매개 변수가 문서화되고 그 의미뿐만 아니라 기본값 타이핑하는 동안.

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

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

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

초기코드

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 의도하지 않은 영향을 미칠 경우 해당 이벤트는 더 이상 심각한 이벤트로 처리되지 않습니다.

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

이와 동일한 문제에는 다양한 형태가 있다는 점에 유의하십시오.또 다른 형태가 설명되어 있습니다. 여기.

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

이러한 이유로 나는 다른 중요한 고려 사항이 없는 한 공개 API(또는 광범위하게 사용될 수 있는 경우 공개 메소드)에서 선택적 매개변수를 사용하지 않을 것입니다.

옵션 매개 변수, 메소드 오버로드는 자체 이점 또는 단점이 있으며, 선호도 중에서 선택할 수있는 선호에 따라 다릅니다.

선택적 매개 변수 : .NET 4.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).

이 링크에 대한 자세한 분석을 찾을 수 있습니다. 선택적 매개 변수를 피합니다 그리고 널 매개 변수를 피합니다

그들이 당신의 API를 처음부터 모델링 할 수있는 두 가지 개념적으로 동등한 두 가지 방법이지만, 불행히도 야생의 기존 고객에 대한 런타임 뒤로 호환성을 고려해야 할 때 미묘한 차이가 있습니다. 내 동료 (감사합니다 Brent!) 멋진 게시물 : 선택적 인수가있는 버전 관리 문제. 일부 인용문 :

옵션 매개 변수가 C# 4에 도입 된 이유는 COM Interop을 지원하는 것이 었습니다. 그게 다야. 그리고 지금, 우리는이 사실의 전체 영향에 대해 배우고 있습니다. 옵션 매개 변수가있는 메소드가있는 경우 컴파일 타임 중단 변경을 일으킬 염려로 추가 선택적 매개 변수가있는 오버로드를 추가 할 수 없습니다. 그리고 기존 오버로드를 제거 할 수는 없습니다. 이것은 항상 런타임이 중단되었습니다. 당신은 그것을 인터페이스처럼 취급해야합니다. 이 경우 유일한 상환은 새 이름으로 새로운 방법을 작성하는 것입니다. 따라서 API에서 선택적 인수를 사용할 계획이라면이를 알고 있어야합니다.

옵션 대신 과부하를 사용하는시기에없는 사람을 추가하려면 다음과 같습니다.

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

더 일반적으로, 방법 서명이 의미가없는 사용 패턴을 활성화 할 때마다 가능한 통화의 순열 수를 제한하십시오. 예를 들어, 옵션 대신 오버로드를 사용함으로써 (이 규칙은 동일한 데이터 유형의 여러 매개 변수가있을 때도 적용됩니다.

예시:

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);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top