2009-09-27 5 views
3

.NET Framework의 Xml.Schema 클래스를 사용하여 XML 스키마를 처리하는 C# 코드가 있습니다. Xml.Schema.XmlSchemaFacet에서 파생 된 클래스의 전체 집합으로서 다양한 단순 유형 제한이 프레임 워크에서 추상화됩니다. 필자가 놓친 바가 없으면 파생 된 패싯 유형 중 어떤 패싯 유형을 알 수있는 유일한 방법은 실패한 경우 결과로 발생하는 InvalidCastOperation을 잡아내어 추론하여 그 중 하나에 캐스팅하는 것입니다. 이렇게하면 다음과 같은 추악한 함수가 생깁니다.try-and-catch-wrapped 투기 캐스팅 작업의 긴 체인을 리팩토링하는 방법

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    try 
    { 
     handler.Length((XmlSchemaLengthFacet)facet); 
    } 
    catch(InvalidCastException) 
    { 
     try 
     { 
      handler.MinLength((XmlSchemaMinLengthFacet)facet); 
     } 
     catch(InvalidCastException) 
     { 
      try 
      { 
       handler.MaxLength((XmlSchemaMaxLengthFacet)facet); 
      } 
      catch(InvalidCastException) 
      { 
       ... 
      } 
     } 
    } 
} 

이렇게하려면 좀 더 우아한 방법이 있어야한다고 가정합니다. .NET 프레임 워크에서 놓친 일부 속성 또는 OO 속임수의 영리한 조각을 사용합니다. 누구나 나를 계몽시킬 수 있습니까?

답변

7

, 나는 코드가 XmlSchemaFacet 서브 클래스를 모두 처리했다 특히, 이런 식으로 할 것 t는 알려진 유형입니다. 모든 처리기 메서드는 XmlSchemaFacet에서 인수를 캐스팅해야하므로 총 코드 줄을 절약 할 수는 있지만 코드를 통한 경로 수는 확실히 절약 할 수 있습니다.

지도가 사전 빌드되어 있다고 가정하면 사전을 사용하는 메소드에 매핑하는 유형이 선형 유형 목록을 탐색하는 것보다 빠르다. 본질적으로 if 블럭을 사용하는 것이 .

+0

I * really *이 모양을 좋아합니다. 지금 내 코드에서 그것을 시험해보고 있는데, 들어간 후에 upvote로 되돌아 올 것이다. –

+1

와우, 다시 한번 고마워. 잘 작동하고 코드가 훨씬 깨끗 해졌다. 또한 사용자가 지정하면 새 코드가 더 빨리 실행됩니다. 이 변경으로 시스템의 단위 테스트 수가 늘어 났지만 NUnit이보고 한 시간은 상당히 줄었습니다. 내가 한 번 이상 upvote 수 있다면, 나는 것입니다. :) –

+0

+1 * 많이 * 여기 다른 모든 답변보다. 이것이 내가 Bevan의 대답에 대한 제 코멘트에서 바라는 바입니다. – MusiGenesis

3

as 키워드를 사용해 볼 수 있습니다. 전송에 실패하면 예외 대신 null이 표시됩니다. 당신이 클래스에 대한 제어를 가지고 있다면

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    var length = facet as XmlSchemaLengthFacet; 
    if (length != null) 
    { 
     handler.Length(length); 
     return; 
    } 

    var minlength = facet as XmlSchemaMinLengthFacet; 
    if (minlength != null) 
    { 
     handler.MinLength(minlength); 
     return; 
    } 

    var maxlength = facet as XmlSchemaMaxLengthFacet; 
    if (maxlength != null) 
    { 
     handler.MaxLength(maxlength); 
     return; 
    } 
    ... 
} 

, 좀 더 깨끗하게 유형 정보를 복구하기 위해 Visitor 패턴 (일명 Double Despatch의 변형을 사용하는 것이 좋습니다 싶지만, 당신이하지 않기 때문에, 이것은 하나의 비교적 간단한 방법입니다 .

업데이트 다음 as 캐스트의 결과를 저장하는 변수를 사용하여 두 번 유형 검사 논리를 통과 할 필요가 피할 수

업데이트 2이 :. C# 4를 사용할 수있게되면 , 당신은 당신을 위해 파견 할 dynamic을 사용할 수 있습니다 : 동적 객체에 대한 방법을 파견 정상적인 방법 오버라이드과 같은 규칙을 사용하기 때문에이 작동합니다

public class HandlerDemo 
{ 
    public void Handle(XmlSchemaLengthFacet facet) { ... } 
    public void Handle(XmlSchemaMinLengthFacet facet) { ... } 
    public void Handle(XmlSchemaMaxLengthFacet facet) { ... } 
    ... 
} 

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    dynamic handler = new HandlerDemo(); 
    handler.Handle(facet); 
} 

, 대신 컴파일의 런타임에서 평가 -시각.

동적 언어 런타임 (DLR)은이 (및 기타 답변)에 표시된 코드와 거의 같은 종류의 트릭을 수행하지만 성능을 위해 캐싱을 추가합니다.

+0

정말 좋아요, 아니면 MusiGenesis의 답변입니까? – ChaosPandion

+0

개인적으로, 나는 여기 (나의 것을 포함해서)의 답을 좋아하지 않는다. 각 유형을 두 번 입력해야하거나 유형을 입력하고 각 유형에 대해 null을 확인해야합니다. 이 메서드에서 실제로 수행하는 것은 각각의 Type을 'handler'에있는 특정 메서드와 연관시키는 것입니다 (그게 무엇이든간에).이 작업을 수행하는 데 더 깨끗한 LINQ-y 방법이 있어야합니다. – MusiGenesis

6

as 키워드를 사용해 볼 수 있습니다. 일부 다른 사람들은 is 키워드를 대신 사용하도록 권장했습니다. as이 더 좋은 이유에 대한 좋은 설명은 this입니다.

일부 샘플 코드 :

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    XmlSchemaLengthFacet lengthFacet = facet as XmlSchemaLengthFacet; 
    if (lengthFacet != null) 
    { 
    handler.Length(lengthFacet); 
    } 
    else 
    { 
    // Re-try with XmlSchemaMinLengthFacet, etc. 
    } 
} 
+0

고마워, 나는이 답변을 분명히 처음부터 엉망으로 개선했기 때문에 upvoted했지만 Robert의 답변은 코드 청결도 관점에서 훌륭하게 훌륭했기 때문에 그의 반응은 받아 들여졌다. –

+0

@Matt : 벤치마킹 'is' 대'as'에 대한 내 업데이트를 확인하십시오. 나는 이것을 "정말로 중요하지 않습니다"라는 범주에 넣었습니다. – MusiGenesis

4
private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    if (facet is XmlSchemaLengthFacet) 
    { 
     handler.Length((XmlSchemaLengthFacet)facet); 
    } 
    else if (facet is XmlSchemaMinLengthFacet) 
    { 
     handler.MinLength((XmlSchemaMinLengthFacet)facet); 
    } 
    else if (facet is XmlSchemaMaxLengthFacet) 
    { 
     handler.MinLength((XmlSchemaMaxLengthFacet)facet); 
    } 

    // etc. 
} 

업데이트 : 나는 벤치 마크 여기서 논의 된 다른 방법 (isas을) 결정했다.

object c1 = new Class1(); 
int trials = 10000000; 
Class1 tester; 
Stopwatch watch = Stopwatch.StartNew(); 
for (int i = 0; i < trials; i++) 
{ 
    if (c1 is Class1) 
    { 
     tester = (Class1)c1; 
    } 
} 
watch.Stop(); 
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~104 ms 
watch.Reset(); 
watch.Start(); 
for (int i = 0; i < trials; i++) 
{ 
    tester = c1 as Class1; 
    if (tester != null) 
    { 
     // 
    } 
} 
watch.Stop(); 
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~86 ms 
watch.Reset(); 
watch.Start(); 
for (int i = 0; i < trials; i++) 
{ 
    if (c1 is Class1) 
    { 
     // 
    } 
} 
watch.Stop();  
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~74 ms 
watch.Reset(); 
watch.Start(); 
for (int i = 0; i < trials; i++) 
{ 
    // 
} 
watch.Stop();  
MessageBox.Show(watch.ElapsedMilliseconds.ToString()); // ~50 ms 

확인 후 as 키워드를 사용하고, 예상대로의 비용을 빼서, (36 밀리 대 54 밀리 초를 is 키워드를 사용하여 주조에 비해 빠른 : 저는 여기에 사용되는 코드입니다 루프 자체).

is 키워드를 사용하지만다음 하지 주조는 의미 빠른 여전히 (24ms)입니다 is 수표의 일련의 올바른 유형을 발견하고 올바른 유형이 을 식별 될 때 만 한 번 캐스팅이 실제로 더 빠를 수 있습니다 (정확한 유형이 식별되기 전에이 방법으로 수행해야하는 다른 유형 검사의 수에 따라 다름).

그러나이 시험에서의 시도 횟수는 1백만,이며 실제로 어떤 방법을 사용하든별로 차이가 없다는 것을 의미합니다. is을 사용하고 캐스팅에 0.0000054 밀리 초가 걸리고 as을 사용하고 null에 대한 확인에는 0.0000036 밀리 초가 걸립니다 (고대 노트에서).

+0

안녕하세요, 답변 주셔서 감사합니다, 나는 이것을 upvoted지만, 로버트는 아래에 자신의 솔루션에 대한 허용 대답을 가져옵니다. –

+0

@Phil : 문제 없습니다. 나는 그의 대답도 더 좋아합니다. – MusiGenesis

1

이것은 시도를하지 않고이를 수행하는 적절한 방법입니다.

if (facet is XmlSchemaLengthFacet) 
{ 
    handler.Length((XmlSchemaLengthFacet)facet); 
} 
else if (facet is XmlSchemaMinLengthFacet) 
{ 
    handler.MinLength((XmlSchemaMinLengthFacet)facet); 
} 
else if (facet is XmlSchemaMaxLengthFacet) 
{ 
    handler.MaxLength((XmlSchemaMaxLengthFacet)facet); 
} 
else 
{ 
    //Handle Error 
} 
0
  • 사용이 객체가 타입 변환을 위해, 정상 캐스팅보다 빠르다 "로"지정된 유형

  • 사용이며 예외가 발생하지 여부를 결정하기 위해 "입니다" 이 오류에 널 (null) reuturns 대신

당신은 이런 식으로 작업을 수행 할 수 있습니다

private void NavigateFacet(XmlSchemaFacet facet) 
{ 
    if (facet is XmlSchemaLengthFacet) 
    { 
     handler.Length(facet as XmlSchemaLengthFacet); 
    } 
    else if (facet is XmlSchemaMinLengthFacet) 
    { 
     handler.MinLength(facet as XmlSchemaMinLengthFacet); 
    } 
    else if (facet is XmlSchemaMaxLengthFacet) 
    { 
     handler.MaxLength(facet as XmlSchemaMaxLengthFacet); 
    } 
} 
'

Dictionary<Type, Action<XmlSchemaFacet>> HandlerMap = 
    new Dictionary<Type, Action<XmlSchemaFacet>> 
{ 
    {typeof(XmlSchemaLengthFacet), handler.Length}, 
    {typeof(XmlSchemaMinLengthFacet), handler.MinLength}, 
    {typeof(XmlSchemaMaxLengthFacet), handler.MaxLength} 
}; 

HandlerMap[facet.GetType()](facet); 

이는 KeyNotFoundException 경우 facet ISN을 던질거야 : 나는 디버깅 코드를 디버깅 데이터를 선호하기 때문에

+0

"as"를 사용하고! = null을 확인하는 편이 좋습니다. 그런 식으로 문제의 객체를 한 번만 검사하면 코드가 완전히 안전합니다. –

+0

"is"와 "as"를 모두 사용하면 중복됩니다.Bevan의 솔루션 (보다 효율적) 또는 MusiGenesis의 솔루션 (더 간단)을 대신 사용하겠습니다. – TrueWill