C # 4.0 : TimeSpan을 기본값을 가진 선택적 매개 변수로 사용할 수 있습니까?


125

이 두 가지 모두 컴파일 타임 상수 여야한다는 오류를 생성합니다.

void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))

우선 누군가 컴파일 타임에 이러한 값을 결정할 수없는 이유를 설명 할 수 있습니까? 그리고 선택적인 TimeSpan 객체의 기본값을 지정하는 방법이 있습니까?


11
요청한 내용과 관련 new TimeSpan(2000)이 없지만 2000 밀리 초를 의미하지 않는다는 것은 0.2 밀리 초 또는 2 만분의 1 초인 2000 "틱"을 의미합니다.
Jeppe Stig Nielsen

답변:


172

서명을 변경하면이 문제를 매우 쉽게 해결할 수 있습니다.

void Foo(TimeSpan? span = null) {

   if (span == null) { span = TimeSpan.FromSeconds(2); }

   ...

}

자세히 설명해야합니다-예제의 표현식이 컴파일 타임 상수가 아닌 이유는 컴파일 타임에 컴파일러가 TimeSpan.FromSeconds (2.0)을 실행하고 결과 바이트를 컴파일 된 코드에 넣을 수 없기 때문입니다.

예를 들어 DateTime.Now를 대신 사용하려고했는지 고려하십시오. DateTime.Now의 값은 실행될 때마다 변경됩니다. 또는 TimeSpan.FromSeconds가 중력을 고려했다고 가정하십시오. 터무니없는 예제이지만 컴파일 타임 상수 규칙은 TimeSpan.FromSeconds가 결정적이라는 것을 알기 때문에 특별한 경우를 만들지 않습니다.


15
이제 <param>서명에 표시되지 않으므로 기본값을 문서화하십시오 .
대령 패닉

3
나는 이것을 할 수 없다. 나는 다른 것에 특별한 값 null을 사용하고있다.
대령 패닉

4
@MattHickford-그런 다음 오버로드 된 메소드를 제공하거나 매개 변수로 밀리 초를 가져와야합니다.
Josh

19
span = span ?? TimeSpan.FromSeconds(2.0);메소드 본문에서 널 입력 가능 유형과 함께 사용할 수도 있습니다 . 또는 var realSpan = span ?? TimeSpan.FromSeconds(2.0);nullable이 아닌 로컬 변수를 가져옵니다.
Jeppe Stig Nielsen

5
내가 마음에 들지 않는 것은 함수의 사용자 에게이 함수가 널 스팬으로 "작동"한다는 것을 암시한다는 것입니다. 그러나 그것은 사실이 아닙니다! 함수의 실제 논리에 관한 한 널은 유효 범위 값 이 아닙니다 . 코드 냄새처럼 보이지 않는 더 좋은 방법이 있었으면 좋겠다 ...
JoeCool

31

내 VB6 유산은 "널 값"과 "결측 값"을 동등한 것으로 간주한다는 생각으로 인해 불편합니다. 대부분의 경우 문제는 없지만, 의도하지 않은 부작용이 있거나 예외적 인 조건을 삼킬 수 있습니다 (예 : 소스 span가 null이 아니어야하지만 속성이 아닌 속성 또는 변수 인 경우).

따라서 메서드를 오버로드합니다.

void Foo()
{
    Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
    //...
}

1
그 위대한 기술에 +1. 기본 매개 변수는 const 유형에만 사용해야합니다. 그렇지 않으면 신뢰할 수 없습니다.
Lazlo

2
이것은 기본값이 대체 된 '시간 우호적 인'접근법이며,이 상황에서는 이것이 가장 추악한 답변이라고 생각합니다.) 자체적으로 인터페이스에 대해 그렇게 잘 작동하지는 않습니다. 한 곳. 이 경우 확장 메서드가 유용한 도구라는 것을 알게되었습니다. 인터페이스에는 모든 매개 변수가있는 하나의 메서드가 있고 인터페이스와 함께 정적 클래스에서 선언 된 일련의 확장 메서드는 다양한 오버로드에서 기본값을 구현합니다.
OlduwanSteve

23

이것은 잘 작동합니다 :

void Foo(TimeSpan span = default(TimeSpan))


4
스택 오버플로에 오신 것을 환영합니다. 귀하의 대답 은 컴파일러가 허용하는 매우 구체적인 값인 한 기본 매개 변수 값을 제공 할 있다는 것 같습니다 . 내가 이해 했어? (당신은 할 수 있습니다 편집 명확하게 답을.)는 컴파일러 문제는 원래 임의의 것이 었습니다 어떤 노력 무엇을 얻을 수 있습니다 어떤 장점이 걸릴하는 방법을 보여 경우에 더 나은 해답이 될 것입니다 다른 TimeSpan 예에 의해 주어진 것과 같은 값을, new TimeSpan(2000).
Rob Kennedy

2
특정 기본값을 사용하는 대안은 전용 정적 읽기 전용 TimeSpan defaultTimespan = Timespan.FromSeconds (2)를 기본 생성자 및 생성자와 결합하여 시간 범위를 사용하는 것입니다. 공공 푸 () :이 (defaultTimespan)과 공공 푸 (시간 범위의 TS)
요한 마르텐 슨

15

기본값으로 사용할 수있는 값 세트는 속성 인수에 사용할 수있는 것과 같습니다. 기본값이의 내부 메타 데이터로 인코딩되기 때문 DefaultParameterValueAttribute입니다.

컴파일 타임에 결정할 수없는 이유에 관해서. 컴파일 타임에 허용되는 값에 대한 값과 표현식은 공식 C # 언어 사양에 나열되어 있습니다 .

C # 6.0-속성 매개 변수 유형 :

속성 클래스의 위치 및 명명 된 매개 변수 유형은 다음과 같은 속성 매개 변수 유형으로 제한됩니다 .

  • 다음 유형 중 하나 : bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • 유형 object입니다.
  • 유형 System.Type입니다.
  • 열거 형입니다.
    (공개 접근성 및 중첩 유형 (있는 경우)도 공개 접근성 제공)
  • 위 유형의 1 차원 배열.

유형 TimeSpan은 이러한 목록에 맞지 않으므로 상수로 사용할 수 없습니다.


2
약간의 nit-pick : 정적 메소드 호출이 목록에 맞지 않습니다. TimeSpan이 목록의 마지막 항목에 맞을 수 있습니다 default(TimeSpan).
코드 InChaos

12
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값이 함수에 유효한 값일 가능성 이 희박하다는 점을 고려하면 더 좋습니다 .


4

TimeSpan의 특별한 경우이며 메소드 DefaultValueAttribute를 통해 구문 분석 할 수있는 문자열을 사용하여 지정됩니다 TimeSpan.Parse.

[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }

3

나의 제안:

void A( long spanInMs = 2000 )
{
    var ts = TimeSpan.FromMilliseconds(spanInMs);

    //...
}

BTW TimeSpan.FromSeconds(2.0)가 같지 않음 new TimeSpan(2000)-생성자가 틱을 걸립니다.


2

다른 답변 은 선택적 매개 변수가 동적 표현식이 될 수없는 이유에 대한 훌륭한 설명을 제공했습니다. 그러나 다시 말해 기본 매개 변수는 컴파일 시간 상수처럼 작동합니다. 즉, 컴파일러는이를 평가하고 답을 얻을 수 있어야합니다. 상수 선언이 발생할 때 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

단점

이것은이 문제를 해결하기위한 헤비급 접근법입니다. 빠르고 더러운 내부 인터페이스를 작성하고 TimeSpannull을 허용하고 원하는 기본값처럼 null을 처리 하면 대신 작동합니다.

또한 많은 수의 매개 변수가 있거나 타이트한 루프에서 메서드를 호출하는 경우 클래스 인스턴스화 오버 헤드가 발생합니다. 물론, 그러한 메소드를 꽉 루프로 호출하면 자연스럽고 FooSettings객체 의 인스턴스를 재사용하는 것이 매우 쉽습니다 .

혜택

예제의 주석에서 언급했듯이이 패턴은 퍼블릭 API에 적합하다고 생각합니다. 클래스에 새 속성을 추가하는 것은 ABI의 주요 변경 사항이 아니므로이 패턴을 사용하여 메서드의 서명을 변경하지 않고도 새로운 선택적 매개 변수를 추가 할 수 있습니다. 즉, 추가 작업없이 이전 컴파일 된 코드를 계속 지원하면서 더 최근에 컴파일 된 코드에 더 많은 옵션을 제공합니다. .

또한 C #에 내장 된 기본 메서드 매개 변수는 컴파일 타임 상수로 처리되어 호출 사이트에 구워 지므로 기본 매개 변수는 다시 컴파일 된 코드에서만 사용됩니다. 설정 객체를 인스턴스화하면 호출자가 메소드를 호출 할 때 기본값을 동적으로로드합니다. 즉, 설정 클래스 만 변경하면 기본값을 업데이트 할 수 있습니다. 따라서이 패턴을 사용하면 원하는 경우 새 값을보기 위해 호출자를 다시 컴파일하지 않고도 기본값을 변경할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.