2009-05-08 3 views
5

나는 시험을 다음과 같이 내 코드베이스 불변의 규칙을 적용 할.NET 불변의 객체

[TestFixture] 
public class TestEntityIf 
{ 
    [Test] 
    public void IsImmutable() 
    { 
     var setterCount = 
      (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance) 
      where s.CanWrite 
      select s) 
       .Count(); 

     Assert.That(setterCount == 0, Is.True, "Immutable rule is broken"); 
    } 
} 

그것은을 위해 전달합니다

public class Entity 
{ 
    private int ID1; 
    public int ID 
    { 
     get { return ID1; } 
    } 
} 

하지만이를 위해하지 않습니다

public class Entity 
{ 
    public int ID { get; private set; } 
} 

그리고 여기에 "WTF?"라는 질문이 있습니다.

답변

3

어딘가에 게시 된 답변의 작은 수정. 다음은 보호되거나 공개 된 설정자가있는 속성이 하나 이상있는 경우 0이 아닌 값을 반환합니다. GetSetMethod가 IsPublic (public 전용)이 아닌 null (setter가 없음) 및 IsPrivate (즉 public 또는 protected가 아닌)에 대한 테스트를 반환하는지 확인합니다. pointed out in Daniel Brückner's answer 그럼에도 불구하고

var setterCount = 
      (from s in typeof(Entity).GetProperties(
       BindingFlags.Public 
       | BindingFlags.NonPublic 
       | BindingFlags.Instance) 
      where 
       s.GetSetMethod(true) != null // setter available 
       && (!s.GetSetMethod(true).IsPrivate) 
      select s).Count(); 

는, 클래스가 더 공개적으로 visible 속성 세터가없는 사실은 필요하지만, 클래스가 아니라 충분 조건은 불변 고려되어야한다.

+0

CanWrite == true는 setter의 존재를 보장하므로 s.GetSetMethod (true)! = null 테스트는 불필요합니다. –

+0

사실, "s.GetSetMethod (true)! = null"은 CanWrite가 참인 것과 동일하므로 그 중 하나가 중복됩니다. IsPrivate 속성을 역 참조하기 전에 null 테스트를 명시 적으로 수행하고자하므로 CanWrite를 제거했습니다. – Joe

1

확실히 두 번째 것은 private set입니다.

먼저 Entity 클래스의 인스턴스는 외부 클래스에서 ID1 속성을 쓸 수 있습니다. 후자에서

는 세터는 클래스 자체에 개인이며, 그렇게 만 (즉, 그것의 자신의 생성자/초기화)이

두 번째에 세터 밖으로 private을 가지고 그것을해야 엔티티 내에서 호출 할 수 있습니다 패스

+0

ID1은 첫 번째 클래스의 속성이 아니며 getter를 통해 외부로 읽기 전용입니다. 외부 수업에서 어떻게 접근 가능합니까? –

+0

그의 원래 예에서 그는 첫 번째 속성에 대한 공개 설정자를 가졌습니다. 그는 이후 질문을 편집했습니다. http://stackoverflow.com/revisions/839066/list –

+0

나는 본다. 감사합니다 Eoin. –

2

CanWrite는 setter가 있으면 true를 반환한다고합니다. 개인적인 세터는 또한 세터입니다.

공개 설정자가 있기 때문에 첫 번째 패스가 약간 놀랍습니다. 그래서 내가 카페에서 너무 낮지 않으면 settercount가 1이되어 어설 션이 실패해야합니다. CanWrite는 두 가지 모두에 대해 사실을 반환하기 때문에 둘 다 실패해야합니다. (그리고 linq 쿼리는 ID를 포함한 공개 속성을 공개적으로 검색합니다.)

(편집) 이제는 첫 번째 클래스의 코드가 변경된 것을보고 이제는 더 이상 설정자가 없습니다.

그래서 CanWrite가 setter 메서드의 접근자를 살펴 본다는 가정은 사실이 아닙니다. 당신은해야한다 :

var setterCount = 
      (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance) 
      where s.GetSetMethod().IsPublic 
      select s) 
       .Count(); 
+0

방금 ​​코드를 시도했지만 동의합니다. 두 가지 테스트 클래스 모두 1 세트 씩 계산됩니다. –

+0

제가 캐페인도 너무 낮지 않으면, 첫 번째 경우에는 세터가 보이지 않습니다. ID1은 멤버 변수이므로 속성이 아니므로 "setter"메서드가 없습니다. 정확히 –

+0

. TS가 대신 수행해야하는 것은 propertyinfo에서 GetSetMethod()를 호출 한 다음 반환 된 MethodInfo에서 IsPublic이 true인지 확인합니다. –

7

문제는 개인 소유의 세터로 인해 퍼블릭 게터 때문에 속성이 공개되어 쓰기 가능하다는 점이다. 시험을 수정해야합니다.

추가 정보 메서드 내에서 개인 데이터를 수정할 수 있으므로이 방법으로 불변성을 보장 할 수 없습니다. 불변성을 보장하려면 모든 필드가 읽기 전용으로 선언되고 자동 구현 된 속성이 없는지 확인해야합니다. 이 확장 방법으로

public static Boolean IsImmutable(this Type type) 
{ 
    const BindingFlags flags = BindingFlags.Instance | 
           BindingFlags.NonPublic | 
           BindingFlags.Public; 

    return type.GetFields(flags).All(f => f.IsInitOnly); 
} 

public static Boolean IsImmutable(this Object @object) 
{ 
    return (@object == null) || @object.GetType().IsImmutable(); 
} 

쉽게 유형

typeof(MyType).IsImmutable() 

및 인스턴스 자신의 불변성에 대한

myInstance.IsImmutable() 

을 테스트 할 수 있습니다. 인스턴스 필드에서 찾고

참고

  • 당신이 쓰기 가능한 속성을 가지고 있습니다,하지만 지금은 수정할 수 필드 있다는 것을 보장합니다.
  • 개인 구현의 익명 지원 필드 때문에 자동 구현 된 속성이 예상대로 immutability 테스트에 실패합니다.
  • 리플렉션을 사용하여 readonly 개의 입력란을 수정할 수 있습니다.
  • 아마도이 개체가 상태에 속하기 때문에 모든 필드의 형식이 변경되지 않을 수도 있습니다.
  • 순환이 발생할 수 있고 기본 유형이 변경 가능하기 때문에 모든 필드에 대해 간단한 FiledInfo.FieldType.IsImmutable()을 사용하여이 작업을 수행 할 수 없습니다. 설정 - 방법 및 GET-방법은 동일한 액세스 수정이 없기 때문에
+0

'object'와'bool' 대신'Object'와'Boolean'을 사용해야하는 이유는 무엇입니까? –

+0

그들은 더 멋지게 보입니다. (구문 하이라이팅과 대문자 첫 글자) 실제 유형이고 문자열, 부울, int, 객체 및 기타 모든 것에 대해 컴파일러가 생성하는 것에 의존하지 않습니다. (컴파일러에는 선택의 여지가 없습니다.). –

+0

이에 대한 좋은 이유가 있습니까? –

3

당신은 아마 당신이 PropertyInfo 자체에 의존 할 수 없다,

propertyInfo.GetSetMethod().IsPublic 

를 호출해야합니다.

var setterCount = 
     (from s in typeof (Entity).GetProperties(
      BindingFlags.Public 
      | BindingFlags.NonPublic 
      | BindingFlags.Instance) 
     where 
      s.GetSetMethod() != null  // public setter available 
     select s) 
+0

+1 거의 다 왔어. 그러나 GetSetMethod()는 개인 설정자 또는 설정자가없는 속성에 대해 null을 반환합니다. null을 테스트하려면이 값을 변경해야합니다. – Joe

+0

@Joe : Ok, 더 이상 CanWrite가 필요하지 않습니다. –

+0

ProPrtyInfo.GetSetMethod()는 ProeprtyInfo를 호출하지 않는 한 public set 메서드 만 반환하기 때문에 s.GetSetMethod(). IsPublic이 필요하지 않습니다. GetSetMethod (Boolean nonPublic)입니다. –

1

올바르게 이해했다면 엔터티를 변경할 수 없기를 원할 것입니다.

s.CanWrite && s.GetSetMethod().IsPublic 
+0

어설 션이 정확합니다. setterCount == 0이 true이고이 테스트에 실패하면 어설 션에 "변경 가능 규칙이 손상되었습니다."라는 메시지가 표시되면 유형이 변경되지 않습니다. –

+0

Fixed, thanks Daniel – SeeR

2

변경해야하는 경우 나에 재산 상태의 변경을 제안 LINQ 쿼리에서 반환됩니다? 분명히, 그것은 당신이 기대하지 않는 것을 집어 들고 있습니다. 이러한 지식을 바탕으로 LINQ 쿼리를 원하는 방식으로 작업 할 수 있도록 선택할 수 있습니다. 또한 @Daniel Bruckner는 필드가 읽기 전용이 아니라면 클래스가 완전히 변경 가능하지 않다는 것에 동의합니다. 호출 측은, getter를 개입시켜 읽기 전용으로 공개되고있는 비공개 필드를 내부적으로 변경하는 메서드를 호출하거나 getter를 사용할 수 있습니다.이 메서드는 불변의 규칙을 깨고, 클라이언트를 놀라게하여, 문제를 일으 킵니다. 변경 가능한 객체에 대한 작업에는 부작용이 없어야합니다.

0

당신이 누구 CanWrite라고 재산 진실이 무엇인지 특성을보고 그것을 디버깅을 시도 유무 : 그래서, 다음 테스트가

var setterCount = (from s in typeof(string).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(p => p.GetSetMethod()) 
    where s != null && s.IsPublic 
    select s).Count(); 

Assert.That(setterCount == 0, Is.True, "Immutable rule is broken");