어떻게:단락 역 삼항 연산자가 구현되었습니다.씨#?그게 그렇게 중요한 건가?

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

문제

객체에 대한 할당을 선택하기 위해 삼항 연산자, null 병합 연산자 또는 중첩된 if-else 문을 사용한다고 가정합니다.이제 조건문 내에서 결과를 임시 변수에 입력하고 해당 상태를 캡처하여 비교한 다음 잠재적으로 할당해야 하는 비용이 많이 들고 일시적인 작업을 평가한다고 가정해 보겠습니다.

C#과 같은 언어에서는 이 경우를 처리하기 위해 새로운 논리 연산자를 어떻게 구현합니까?그래야 할까요?C#에서 이 경우를 처리할 수 있는 기존 방법이 있습니까?다른 언어?

예를 들어 직접 비교를 찾고 있다고 가정할 때 삼항 또는 널 병합 연산자의 장황함을 줄이는 일부 사례가 극복되었습니다.보다 Null Coalescing 연산자를 사용하는 독특한 방법, 특히 지원을 위해 연산자의 사용을 확장할 수 있는 방법에 대한 논의 String.IsNullOrEmpty(string).방법 참고 존 스키트 을 사용하고 있습니다 PartialComparer ~에서 MiscUtil, 다시 포맷하려면 0~에게 null에스,

이것이 왜 필요한가요?자, 지름길 없이 복잡한 객체에 대한 비교 방법을 작성하는 방법을 살펴보십시오(인용된 토론의 예):

public static int Compare( Person p1, Person p2 )
{
    return ( (result = Compare( p1.Age, p2.Age )) != 0 ) ? result
        : ( (result = Compare( p1.Name, p2.Name )) != 0 ) ? result
        : Compare( p1.Salary, p2.Salary );
}

Jon Skeet은 평등 사례를 대체하기 위해 새로운 비교를 작성합니다.이를 통해 null을 반환하는 새로운 특정 메서드를 작성하여 표현식을 확장할 수 있으므로 null 병합 연산자를 사용할 수 있습니다.

return PartialComparer.Compare(p1.Age, p2.Age)
    ?? PartialComparer.Compare(p1.Name, p2.Name)
    ?? PartialComparer.Compare(p1.Salary, p2.Salary)
    ?? 0;

Null 병합 연산자는 3개의 변이 아닌 2개의 변을 갖기 때문에 더 읽기 쉽습니다.부울 조건 절은 메소드로 분리되어 있습니다. 이 경우에는 다음을 반환합니다. null 표현식이 계속되어야 하는 경우.

조건을 더 쉽게 인라인으로 넣을 수 있다면 위의 표현식은 어떤 모습일까요?에서 표현을 가져오세요. PartialComparer.Compare 이는 반환 null, 암시적 임시 변수와 함께 왼쪽 표현식의 평가를 사용할 수 있는 새로운 삼항 표현식에 배치합니다. value:

return Compare( p1.Age, p2.Age ) unless value == 0
     : Compare( p1.Name, p2.Name ) unless value == 0
     : Compare( p1.Salary, p2.Salary );

표현식의 기본 "흐름"은 다음과 같습니다.

표현 ~하지 않는 한 부울 어떤 경우에는 표현

과부하된 비교 연산자라기보다는 단락 회로에 가깝다고 생각합니다. 역삼항 연산자.

  • 이런 유형의 논리가 유용할까요?현재 Null 병합은 조건식을 사용하여 이를 수행하는 방법을 제공합니다. (value == null).
  • 어떤 다른 표현에 대해 테스트하고 싶나요?우리는 들어 본 적이 있습니다 (String.IsNullOrEmpty(value)).
  • 이를 연산자, 키워드 측면에서 언어로 표현하는 가장 좋은 방법은 무엇일까요?
도움이 되었습니까?

해결책

개인적으로 나는 연산자의 단락을 피하고 메서드를 연결하도록 허용합니다.

public static int CompareChain<T>(this int previous, T a, T b)
{
    if (previous != 0)
        return previous;
    return Comparer<T>.Default.Compare(a,b);
}

다음과 같이 사용하세요:

int a = 0, b = 2;
string x = "foo", y = "bar";
return a.Compare(b).CompareChain(x,y);

JIT에 의해 인라인될 수 있으므로 더 복잡해지지 않고 언어에 내장된 단락 기능을 수행할 수 있습니다.

위의 '구조'가 단순한 비교 이상에 적용될 수 있는지 묻는 질문에 대한 응답으로, 계속할지 여부를 사용자가 명시적이고 제어할 수 있도록 선택함으로써 가능합니다.이는 본질적으로 더 복잡하지만 작업이 더 유연하므로 이는 불가피합니다.

public static T ElseIf<T>(
    this T previous, 
    Func<T,bool> isOK
    Func<T> candidate)
{
    if (previous != null && isOK(previous))
        return previous;
    return candidate();
}

그런 다음 그렇게 사용하십시오

Connection bestConnection = server1.GetConnection()
    .ElseIf(IsOk, server2.GetConnection)
    .ElseIf(IsOk, server3.GetConnection)
    .ElseIf(IsOk, () => null);

이는 다음을 변경할 수 있다는 점에서 최대의 유연성을 제공합니다. 괜찮아요 어떤 단계에서든 확인하고 완전히 게으르다.OK 확인이 모든 경우에 동일한 상황에서는 이렇게 단순화하고 확장 방법을 완전히 피할 수 있습니다.

public static T ElseIf<T>(        
    Func<T,bool> isOK
    IEnumerable<Func<T>[] candidates)
{
   foreach (var candidate in candidates)
   { 
        var t = candidate();
        if (isOK(t))
            return t;
   }
   throw new ArgumentException("none were acceptable");
}

linq로 이 작업을 수행할 수 있지만 이 방법은 좋은 오류 메시지를 제공하고 이를 허용합니다.

public static T ElseIf<T>(        
    Func<T,bool> isOK
    params Func<T>[] candidates)
{
    return ElseIf<T>(isOK, (IEnumerable<Func<T>>)candidates);
}

다음과 같이 읽기 좋은 코드로 이어지는 스타일:

var bestConnection = ElseIf(IsOk,
    server1.GetConnection,
    server2.GetConnection,
    server3.GetConnection);

기본값을 허용하려면 다음을 수행하십시오.

public static T ElseIfOrDefault<T>(        
    Func<T,bool> isOK
    IEnumerable<Func<T>>[] candidates)
{
   foreach (var candidate in candidates)
   { 
        var t = candidate();
        if (isOK(t))
            return t;
   }
   return default(T);
}

분명히 위의 모든 내용은 람다를 사용하여 매우 쉽게 작성할 수 있으므로 구체적인 예는 다음과 같습니다.

var bestConnection = ElseIfOrDefault(
    c => c != null && !(c.IsBusy || c.IsFull),
    server1.GetConnection,
    server2.GetConnection,
    server3.GetConnection);

다른 팁

당신은 이미이 질문에 대한 좋은 답변을 많이 받았으며, 나는이 특정 파티에 늦었습니다. 그러나 귀하의 제안은 C#이 가졌던 더 일반적으로 유용한 작업의 특별한 경우, 즉 표현 맥락에서 임시 계산에 이름을 제시하는 능력의 특별한 경우라는 점에 주목할 가치가 있다고 생각합니다.

실제로 C#에는이 연산자가 있지만 쿼리 이해 만하면됩니다. C# 3의 연산자로 이것을 추가 할 수 있었으면 좋겠다.

public static int Compare(Person p1, Person p2) =>
  let ages = Compare(p1.Age, p2.Age) in
    ages != 0 ? 
      ages : 
      let names = Compare(p1.Name, p2.Name) in
      names != 0 ? 
        names : 
        Compare(p1.Salary, p2.Salary);

"표현"은 너무 유용합니다, 그리고 발견 된 너무 적은 언어, 그리고 나는 언어 디자이너가 왜 버전 1에 즉시 추가하지 않는지 진정으로 이해하지 못합니다.

C# 이이 기능을 가지고 있다면 제안이 있습니다.

A() unless B() : C()

간단합니다

let a = A() in B() ? C() : a

이해하기 어려운 것은 거의없고 보너스를 사용합니다. a 표현으로 B() 그리고 C() 당신이 좋아한다면.

람다가있는 모든 언어로 표현을 모방 할 수 있습니다. 물론이야 let x = y in z 간단합니다 (x=>z)(y), 그러나 C#은 모든 람다에서 대의원 유형으로 변환해야하기 때문에 C#에이를 작성하는 간결한 방법은 없습니다.

또한 Roslyn에서는 우리가 할 수는 있지만 임시를 맹세로 대표하지 않습니다. 오히려, 우리는 그 아래의 한 수준까지 가고 "값을 생성 할 수있는 일련의 연산, 그 중 하나는이 표현의 가치가 될 것"에 대한 표현을 가지고 있습니다. "x = y in z"는 단순히 서열 "x, x = y, z, dallelocate x"입니다. 여기서 세 번째 요소는 값입니다. 그리고 원래의 Pre-Roslyn C# 컴파일러에서 우리는 내부 연산자 "왼쪽"및 "오른쪽"이 있었는데, 이는 두 개의 표현식을 취하고 왼쪽 또는 오른쪽을 생산하는 이진 연산자 였으므로 생성 할 수있었습니다. ((allocate x) right ((x = y) right z)) left (deallocate x).

내 요점은 : 우리는 종종 비정상적인 구두점으로 맞춤형 언어 기능에 대한 요청을 받지만 일반적으로 구현하는 것이 더 좋았을 것입니다. 기본 빌딩 블록 이 운영자를 자연스럽게 구축 할 수 있습니다.

제안 된 하나의 구현을 매우 장황한 질문에서 벗어나려면 unless 예어.

(표현 ) unless (부울 )u003Cmagical "in which case" operator> (표현 )

... 모든 것이있을 것입니다.

부울 표현 표현의 평가에 접근 할 수 있습니다 키워드를 통해 value. 표현 가질 수 있습니다 unless 표현식의 키워드, 단순하고 선형 체인을 허용합니다.

후보자u003Cmagical "in which case" operator> :

  • :
  • |
  • ?:
  • otherwise 예어

모든 기호의 사용은 평균 개발자의 가독성을 감소시키는 경향이 있습니다. 심지어 ?? 연산자는 널리 사용되지 않습니다. 나 자신은 장황 코드를 개발하는 것을 선호하지만 지금부터 1 년 동안 쉽게 읽을 수 있습니다.

그래서 당신의 후보자 :

표현 a 부울 B이 경우 표현 씨.

할 것입니다

표현 a 부울 B 소튼 표현 씨.

나와 같은 많은 사람들이 여전히 사용할 것입니다.

if (B) {expression C;}
else {expression A;}

이것은 당신이 큰 팀, 다른 배경, 각각의 팀 마스터의 한 언어 및 다른 사람들의 사용자가있는 소프트웨어를 개발할 때 발생합니다.

더 @Shuggycouk: 아, 이것이 단순한 비교 이상의 경우에도 효과가있을 수 있습니까? 나는 c# 3과 확장 방법을 사용하지 않았지만 이전의 예에 대해서는 아래의 A를 선언 할 수 있다고 생각합니다.

public delegate bool Validation<T>( T toTest );
public static T Validate<T>( this T leftside, Validation<T> validator )
{
    return validator(leftside) ? leftside : null;
}

그 뒤에, Skeet 당 :

Validation<Connection> v = ( Connection c ) => ( c != null && !( c.IsBusy || c. IsFull ) );
Connection bestConnection =
    server1.GetConnection().Validate( v ) ??
    server2.GetConnection().Validate( v ) ??
    server3.GetConnection().Validate( v ) ?? null;

이것이 C#에서 어떻게 작동 하는가? 의견은 감사합니다. 고맙습니다.


응답으로 Shuggycouk:

그렇다면 이것은 C# 3의 확장 방법입니까? 또한, 여기서 결과는 임의의 표현이 아니라 int입니다. 또 다른 비교 방법을 과부하시키는 데 유용합니다. 최상의 연결을 선택하기위한 표현을 원한다고 가정 해 봅시다. 이상적으로는 다음을 단순화하기를 원합니다.

Connection temp;
Connection bestConnection =
    ( temp = server1.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull) ? temp
    :   ( temp = server2.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
        :   ( temp = server3.GetConnection() ) != null && !(temp.IsBusy || temp.IsFull ) ? temp
            : null;

좋아, 그래서 하나는 방법을 가질 수있다

bool IsOk( Connection c )
{
    return ( c != null && !(c.IsBusy || c.IsFull) );
}

생산할 것 :

Connection temp;
Connection bestConnection =
    ( temp = server1.GetConnection() ) && IsOk( temp ) ? temp
    :   ( temp = server2.GetConnection() ) && IsOk( temp ) ? temp
        :   ( temp = server3.GetConnection() ) && IsOk( temp ) ? temp
            : null;

그러나 여기서 비교를위한 방법 체인은 어떻게 작동할까요? 나는 모양을 숙고하고 있습니다.

Connection bestConnection =
    server1.GetConnection() unless !IsOk(value) otherwise
    server2.GetConnection() unless !IsOk(value) otherwise
    server3.GetConnection() unless !IsOk(value) otherwise null;

조건부의 결과가 원래 조건부에 있던 방법의 표현 또는 결과가되기를 원한다면 지금까지의 후프가 있다고 생각합니다.

나는 그러한 방법으로 반환 된 물체가 생산 비용이 많이 들거나 다음에 메소드가 호출 될 때 변경 될 것이라고 가정합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top