답변:
서명을 변경하면이 문제를 매우 쉽게 해결할 수 있습니다.
void Foo(TimeSpan? span = null) {
if (span == null) { span = TimeSpan.FromSeconds(2); }
...
}
자세히 설명해야합니다-예제의 표현식이 컴파일 타임 상수가 아닌 이유는 컴파일 타임에 컴파일러가 TimeSpan.FromSeconds (2.0)을 실행하고 결과 바이트를 컴파일 된 코드에 넣을 수 없기 때문입니다.
예를 들어 DateTime.Now를 대신 사용하려고했는지 고려하십시오. DateTime.Now의 값은 실행될 때마다 변경됩니다. 또는 TimeSpan.FromSeconds가 중력을 고려했다고 가정하십시오. 터무니없는 예제이지만 컴파일 타임 상수 규칙은 TimeSpan.FromSeconds가 결정적이라는 것을 알기 때문에 특별한 경우를 만들지 않습니다.
<param>
서명에 표시되지 않으므로 기본값을 문서화하십시오 .
span = span ?? TimeSpan.FromSeconds(2.0);
메소드 본문에서 널 입력 가능 유형과 함께 사용할 수도 있습니다 . 또는 var realSpan = span ?? TimeSpan.FromSeconds(2.0);
nullable이 아닌 로컬 변수를 가져옵니다.
내 VB6 유산은 "널 값"과 "결측 값"을 동등한 것으로 간주한다는 생각으로 인해 불편합니다. 대부분의 경우 문제는 없지만, 의도하지 않은 부작용이 있거나 예외적 인 조건을 삼킬 수 있습니다 (예 : 소스 span
가 null이 아니어야하지만 속성이 아닌 속성 또는 변수 인 경우).
따라서 메서드를 오버로드합니다.
void Foo()
{
Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
//...
}
이것은 잘 작동합니다 :
void Foo(TimeSpan span = default(TimeSpan))
TimeSpan
예에 의해 주어진 것과 같은 값을, new TimeSpan(2000)
.
기본값으로 사용할 수있는 값 세트는 속성 인수에 사용할 수있는 것과 같습니다. 기본값이의 내부 메타 데이터로 인코딩되기 때문 DefaultParameterValueAttribute
입니다.
컴파일 타임에 결정할 수없는 이유에 관해서. 컴파일 타임에 허용되는 값에 대한 값과 표현식은 공식 C # 언어 사양에 나열되어 있습니다 .
C # 6.0-속성 매개 변수 유형 :
속성 클래스의 위치 및 명명 된 매개 변수 유형은 다음과 같은 속성 매개 변수 유형으로 제한됩니다 .
- 다음 유형 중 하나 :
bool
,byte
,char
,double
,float
,int
,long
,sbyte
,short
,string
,uint
,ulong
,ushort
.- 유형
object
입니다.- 유형
System.Type
입니다.- 열거 형입니다.
(공개 접근성 및 중첩 유형 (있는 경우)도 공개 접근성 제공)- 위 유형의 1 차원 배열.
유형 TimeSpan
은 이러한 목록에 맞지 않으므로 상수로 사용할 수 없습니다.
TimeSpan
이 목록의 마지막 항목에 맞을 수 있습니다 default(TimeSpan)
.
void Foo(TimeSpan span = default(TimeSpan))
{
if (span == default(TimeSpan))
span = TimeSpan.FromSeconds(2);
}
제공된 default(TimeSpan)
함수에 유효한 값이 아닙니다.
또는
//this works only for value types which TimeSpan is
void Foo(TimeSpan span = new TimeSpan())
{
if (span == new TimeSpan())
span = TimeSpan.FromSeconds(2);
}
제공된 new TimeSpan()
값이 유효하지 않습니다.
또는
void Foo(TimeSpan? span = null)
{
if (span == null)
span = TimeSpan.FromSeconds(2);
}
이 null
값이 함수에 유효한 값일 가능성 이 희박하다는 점을 고려하면 더 좋습니다 .
다른 답변 은 선택적 매개 변수가 동적 표현식이 될 수없는 이유에 대한 훌륭한 설명을 제공했습니다. 그러나 다시 말해 기본 매개 변수는 컴파일 시간 상수처럼 작동합니다. 즉, 컴파일러는이를 평가하고 답을 얻을 수 있어야합니다. 상수 선언이 발생할 때 C #에서 동적 표현식을 평가하는 컴파일러에 대한 지원을 추가하려는 일부 사람들이 있습니다. 이러한 종류의 기능은 메소드를 "순수"로 표시하는 것과 관련이 있지만 지금은 현실이 아니며 결코 그렇지 않을 수 있습니다.
이러한 방법에 C # 기본 매개 변수를 사용하는 것의 대안은로 예시 된 패턴을 사용하는 것입니다 XmlReaderSettings
. 이 패턴에서 매개 변수가없는 생성자와 공개적으로 쓰기 가능한 특성을 가진 클래스를 정의하십시오. 그런 다음 메소드의 모든 옵션을이 유형의 오브젝트로 기본값으로 바꾸십시오. 기본값을 지정하여이 개체를 선택적으로 만들 null
수도 있습니다. 예를 들면 다음과 같습니다.
public class FooSettings
{
public TimeSpan Span { get; set; } = TimeSpan.FromSeconds(2);
// I imagine that if you had a heavyweight default
// thing you’d want to avoid instantiating it right away
// because the caller might override that parameter. So, be
// lazy! (Or just directly store a factory lambda with Func<IThing>).
Lazy<IThing> thing = new Lazy<IThing>(() => new FatThing());
public IThing Thing
{
get { return thing.Value; }
set { thing = new Lazy<IThing>(() => value); }
}
// Another cool thing about this pattern is that you can
// add additional optional parameters in the future without
// even breaking ABI.
//bool FutureThing { get; set; } = true;
// You can even run very complicated code to populate properties
// if you cannot use a property initialization expression.
//public FooSettings() { }
}
public class Bar
{
public void Foo(FooSettings settings = null)
{
// Allow the caller to use *all* the defaults easily.
settings = settings ?? new FooSettings();
Console.WriteLine(settings.Span);
}
}
호출하려면 하나의 표현식에서 속성을 인스턴스화하고 할당하는 이상한 구문을 사용하십시오.
bar.Foo(); // 00:00:02
bar.Foo(new FooSettings { Span = TimeSpan.FromDays(1), }); // 1.00:00:00
bar.Foo(new FooSettings { Thing = new MyCustomThing(), }); // 00:00:02
이것은이 문제를 해결하기위한 헤비급 접근법입니다. 빠르고 더러운 내부 인터페이스를 작성하고 TimeSpan
null을 허용하고 원하는 기본값처럼 null을 처리 하면 대신 작동합니다.
또한 많은 수의 매개 변수가 있거나 타이트한 루프에서 메서드를 호출하는 경우 클래스 인스턴스화 오버 헤드가 발생합니다. 물론, 그러한 메소드를 꽉 루프로 호출하면 자연스럽고 FooSettings
객체 의 인스턴스를 재사용하는 것이 매우 쉽습니다 .
예제의 주석에서 언급했듯이이 패턴은 퍼블릭 API에 적합하다고 생각합니다. 클래스에 새 속성을 추가하는 것은 ABI의 주요 변경 사항이 아니므로이 패턴을 사용하여 메서드의 서명을 변경하지 않고도 새로운 선택적 매개 변수를 추가 할 수 있습니다. 즉, 추가 작업없이 이전 컴파일 된 코드를 계속 지원하면서 더 최근에 컴파일 된 코드에 더 많은 옵션을 제공합니다. .
또한 C #에 내장 된 기본 메서드 매개 변수는 컴파일 타임 상수로 처리되어 호출 사이트에 구워 지므로 기본 매개 변수는 다시 컴파일 된 코드에서만 사용됩니다. 설정 객체를 인스턴스화하면 호출자가 메소드를 호출 할 때 기본값을 동적으로로드합니다. 즉, 설정 클래스 만 변경하면 기본값을 업데이트 할 수 있습니다. 따라서이 패턴을 사용하면 원하는 경우 새 값을보기 위해 호출자를 다시 컴파일하지 않고도 기본값을 변경할 수 있습니다.
new TimeSpan(2000)
이 없지만 2000 밀리 초를 의미하지 않는다는 것은 0.2 밀리 초 또는 2 만분의 1 초인 2000 "틱"을 의미합니다.