[c#] C #에서 속성을 사용하지 않아야하는 이유는 무엇입니까?

그의 훌륭한 저서 인 CLR Via C #에서 Jeffrey Richter는 속성을 좋아하지 않으며 사용하지 말 것을 권장한다고 말했습니다. 그는 어떤 이유를 줬지만 나는 정말로 이해하지 못한다. 왜 내가 속성을 사용하거나 사용하지 말아야하는지 설명해 줄 수 있습니까? 자동 속성을 사용하는 C # 3.0에서 이것이 변경됩니까?

참고로 Jeffrey Richter의 의견을 추가했습니다.

• 속성은 읽기 전용이거나 쓰기 전용 일 수 있습니다. 필드 액세스는 항상 읽고 쓸 수 있습니다. 속성을 정의하는 경우 get 및 set 접근 자 메서드를 모두 제공하는 것이 가장 좋습니다.

• 속성 메서드에서 예외가 발생할 수 있습니다. 필드 액세스는 예외를 발생시키지 않습니다.

• 속성은 out 또는 ref 매개 변수로 메서드에 전달할 수 없습니다. 필드는 할 수 있습니다. 예를 들어 다음 코드는 컴파일되지 않습니다.

using System;
public sealed class SomeType
{
   private static String Name
   {
     get { return null; }
     set {}
   }
   static void MethodWithOutParam(out String n) { n = null; }
   public static void Main()
   {
      // For the line of code below, the C# compiler emits the following:
      // error CS0206: A property or indexer may not
      // be passed as an out or ref parameter
      MethodWithOutParam(out Name);
   }
}

• 속성 메서드를 실행하는 데 시간이 오래 걸릴 수 있습니다. 필드 액세스는 항상 즉시 완료됩니다. 속성을 사용하는 일반적인 이유는 스레드 동기화를 수행하기위한 것이므로 스레드를 영원히 중지 할 수 있으므로 스레드 동기화가 필요한 경우 속성을 사용하지 않아야합니다. 이러한 상황에서는 방법이 선호됩니다. 또한 클래스에 원격으로 액세스 할 수있는 경우 (예 : 클래스가 System.MashalByRefObject에서 파생 됨) 속성 메서드 호출이 매우 느리므로 속성보다 메서드가 선호됩니다. 제 생각에는 MarshalByRefObject에서 파생 된 클래스는 속성을 사용해서는 안됩니다.

• 연속적으로 여러 번 호출되는 경우 속성 메서드는 매번 다른 값을 반환 할 수 있습니다. 필드는 매번 동일한 값을 반환합니다. System.DateTime 클래스에는 현재 날짜와 시간을 반환하는 readonly Now 속성이 있습니다. 이 속성을 쿼리 할 때마다 다른 값이 반환됩니다. 이것은 실수이며 Microsoft는 Now를 속성 대신 메서드로 만들어 클래스를 수정할 수 있기를 바랍니다.

• 속성 방법은 관찰 가능한 부작용을 일으킬 수 있습니다. 필드 액세스는 절대하지 않습니다. 즉, 유형의 사용자는 유형의 다른 동작을 인식하지 않고 자신이 선택한 순서대로 유형에 정의 된 다양한 속성을 설정할 수 있어야합니다.

• 속성 메서드에는 추가 메모리가 필요하거나 실제로 객체 상태의 일부가 아닌 항목에 대한 참조를 반환 할 수 있으므로 반환 된 객체를 수정해도 원래 객체에는 영향을주지 않습니다. 필드를 쿼리하면 항상 원래 개체 상태의 일부가 보장되는 개체에 대한 참조가 반환됩니다. 복사본을 반환하는 속성으로 작업하는 것은 개발자에게 매우 혼란 스러울 수 있으며이 특성은 종종 문서화되지 않습니다.



답변

Jeff가 속성을 싫어하는 이유는 속성이 필드처럼 보이기 때문입니다. 따라서 차이점을 이해하지 못하는 개발자는 실행 비용이 저렴할 것이라고 가정하고 해당 속성을 필드 인 것처럼 취급합니다.

개인적으로 나는이 특별한 점에 대해 그와 동의하지 않는다. 속성이 클라이언트 코드를 동등한 메서드 호출보다 훨씬 더 읽기 쉽게 만든다는 것을 발견했다. 나는 개발자가 속성이 기본적으로 위장 된 방법이라는 것을 알아야한다는 데 동의합니다.하지만 개발자를 교육하는 것이 방법을 사용하여 코드를 읽기 어렵게 만드는 것보다 낫다고 생각합니다. (특히, 동일한 명령문에서 여러 getter 및 setter가 호출되는 Java 코드를 보았을 때 동등한 C # 코드가 훨씬 더 읽기 쉽다는 것을 알고 있습니다. Demeter의 법칙은 이론적으로는 모두 매우 훌륭하지만 때로는 foo.Name.Length실제로는 그렇습니다. 사용하기에 옳은 것 …)

(아니요, 자동으로 구현 된 속성은 실제로이 사항을 변경하지 않습니다.)

이것은 확장 방법을 사용하는 것에 대한 주장과 약간 비슷합니다. 추론은 이해할 수 있지만 실질적인 이점 (아주 드물게 사용되는 경우)이 제 생각에는 단점보다 큽니다.


답변

글쎄, 그의 주장을 하나씩 살펴 보자.

속성은 읽기 전용이거나 쓰기 전용 일 수 있습니다. 필드 액세스는 항상 읽고 쓸 수 있습니다.

액세스를보다 세밀하게 제어 할 수 있기 때문에 속성에 유리합니다.

속성 메서드는 예외를 throw 할 수 있습니다. 필드 액세스는 예외를 발생시키지 않습니다.

이것은 대부분 사실이지만 초기화되지 않은 개체 필드에서 메서드를 매우 잘 호출하고 예외가 throw 될 수 있습니다.

• 속성은 out 또는 ref 매개 변수로 메서드에 전달할 수 없습니다. 필드는 할 수 있습니다.

공정한.

• 속성 메서드를 실행하는 데 시간이 오래 걸릴 수 있습니다. 필드 액세스는 항상 즉시 완료됩니다.

시간이 거의 걸리지 않을 수도 있습니다.

• 연속적으로 여러 번 호출되는 경우 속성 메서드는 매번 다른 값을 반환 할 수 있습니다. 필드는 매번 동일한 값을 반환합니다.

사실이 아니다. 필드의 값이 변경되지 않았는지 어떻게 알 수 있습니까 (다른 스레드에 의해 가능)?

System.DateTime 클래스에는 현재 날짜와 시간을 반환하는 readonly Now 속성이 있습니다. 이 속성을 쿼리 할 때마다 다른 값이 반환됩니다. 이것은 실수이며 Microsoft는 Now를 속성 대신 메서드로 만들어 클래스를 수정할 수 있기를 바랍니다.

실수라면 사소한 것입니다.

• 속성 방법은 관찰 가능한 부작용을 일으킬 수 있습니다. 필드 액세스는 절대하지 않습니다. 즉, 유형의 사용자는 유형의 다른 동작을 인식하지 않고 자신이 선택한 순서대로 유형에 정의 된 다양한 속성을 설정할 수 있어야합니다.

공정한.

• 속성 메서드에는 추가 메모리가 필요하거나 실제로 객체 상태의 일부가 아닌 항목에 대한 참조를 반환 할 수 있으므로 반환 된 객체를 수정해도 원래 객체에는 영향을주지 않습니다. 필드를 쿼리하면 항상 원래 개체 상태의 일부가 보장되는 개체에 대한 참조가 반환됩니다. 복사본을 반환하는 속성으로 작업하는 것은 개발자에게 매우 혼란 스러울 수 있으며이 특성은 종종 문서화되지 않습니다.

대부분의 항의는 자바의 게터와 세터에 대해서도 언급 될 수 있었으며, 실제로 그러한 문제없이 꽤 오랫동안 겪었습니다.

대부분의 문제는 더 나은 구문 강조 표시 (즉, 필드와 속성을 구별)로 해결할 수 있으므로 프로그래머가 무엇을 기대해야하는지 알 수 있습니다.


답변

나는 책을 읽지 않았고 당신이 이해하지 못하는 부분을 인용하지 않았으므로 추측해야 할 것입니다.

어떤 사람들은 코드가 놀라운 일을 할 수 있기 때문에 속성을 싫어합니다.

를 입력 Foo.Bar하면 일반적으로 읽는 사람들은 이것이 단순히 Foo 클래스의 멤버 필드에 액세스하는 것으로 예상합니다. 저렴하고 거의 무료이며 결정 론적입니다. 반복해서 호출 할 수 있고 매번 같은 결과를 얻을 수 있습니다.

대신 속성을 사용하면 실제로 함수 호출 일 수 있습니다. 무한 루프 일 수 있습니다. 데이터베이스 연결을 열 수 있습니다. 액세스 할 때마다 다른 값을 반환 할 수 있습니다.

Linus가 C ++를 싫어하는 이유와 비슷한 주장입니다. 당신의 코드는 독자들에게 놀랍게 작용할 수 있습니다. 그는 연산자 오버로딩을 싫어 a + b합니다. 반드시 단순한 추가를 의미하지는 않습니다. C # 속성처럼 매우 복잡한 작업을 의미 할 수 있습니다. 부작용이있을 수 있습니다. 그것은 무엇이든 할 수 있습니다.

솔직히 이것은 약한 주장이라고 생각합니다. 두 언어 모두 이와 같은 것으로 가득 차 있습니다. (C #에서도 연산자 오버로딩을 피해야합니까? 결국 동일한 인수를 사용할 수 있습니다.)

속성은 추상화를 허용합니다. 우리는 무언가가 일반 필드 인 것처럼 가장 하여 마치 하나 인 것처럼 사용할 수 있으며 뒤에서 일어나는 일에 대해 걱정할 필요가 없습니다.

그것은 일반적으로 좋은 것으로 간주되지만 분명히 의미있는 추상화를 작성하는 프로그래머에 의존합니다. 귀하의 속성 필드처럼 작동 해야 합니다. 부작용이 없어야하며 비싸거나 안전하지 않은 작업을 수행해서는 안됩니다. 우리 는 그것들 을 필드 라고 생각할 수 있기를 원합니다 .

그러나 나는 그것들이 완벽하지 않다고 생각하는 또 다른 이유가 있습니다. 다른 함수에 대한 참조로 전달할 수 없습니다.

필드를로 전달 ref하면 호출 된 함수가 직접 액세스 할 수 있습니다. 함수는 델리게이트로 전달되어 호출 된 함수가 직접 액세스 할 수 있습니다.

속성 … 할 수 없습니다.

짜증 난다.

그러나 그것이 속성이 악하거나 사용되어서는 안된다는 것을 의미하지는 않습니다. 여러 가지 목적으로 훌륭합니다.


답변

2009 년에이 조언은 Who Moved My Cheese 품종을 헐뜯는 것처럼 보였습니다 . 오늘날, 그것은 거의 우스꽝스럽게 쓸모가 없습니다.

많은 답변이 발끝으로 튀어 나오는 것처럼 보이지만 정면으로 다루지는 않는 매우 중요한 점 중 하나는 이러한 속성의 “위험” 이 프레임 워크 디자인의 의도적 인 부분이라는 것입니다!

예, 속성은 다음을 수행 할 수 있습니다.

  • getter 및 setter에 대해 다른 액세스 수정자를 지정하십시오. 이것은 분야에 비해 장점 입니다. 일반적인 패턴은 공개 getter와 보호 또는 내부 setter를 갖는 것입니다. 이는 필드만으로는 달성 할 수없는 매우 유용한 상속 기술입니다.

  • 예외를 던집니다. 현재까지 이것은 특히 데이터 바인딩 개념이 포함 된 UI 프레임 워크로 작업 할 때 가장 효과적인 유효성 검사 방법 중 하나로 남아 있습니다. 필드 작업을 할 때 개체가 유효한 상태로 유지되도록하는 것이 훨씬 더 어렵습니다.

  • 실행하는 데 오랜 시간이 걸립니다. 여기서 유효한 비교는 필드가 아닌 똑같이 긴 메서드 를 사용하는 것 입니다. 한 저자의 개인적 선호 외에 “방법이 선호 됨”이라는 문구에 대한 근거는 제공되지 않습니다.

  • 후속 실행에서 getter에서 다른 값을 반환합니다. 이것은 / 호출 후 필드의 값이 이전 값과 거의 다를 것이 보장되고 예측할 수없는 필드 가있는 ref/ out매개 변수 의 장점을 칭찬하는 지점에 매우 근접한 농담처럼 보입니다 .refout

    우리가 어떤 심성 커플 링 단일 스레드 액세스의 경우 특정에 대해 이야기 (실제적으로 교육)하는 경우,있어 꽤 잘 이해 가 가시 상태 변경 부작용이 그냥 나쁜 속성 디자인 있다는, 어쩌면 내 기억이다 페이딩이지만 DateTime.Now매번 동일한 가치가 나올 것으로 예상하고 사용하는 사람들의 예를 기억할 수없는 것 같습니다 . 적어도 그들이 가설과 같이 심하게 망쳐 놓지 않았을 경우는 없습니다 DateTime.Now().

  • 눈에 띄는 부작용을 유발합니다. 물론 속성이 처음에 언어 기능으로 발명 된 이유입니다. Microsoft의 자체 Property Design 지침에 따르면 setter 순서는 중요하지 않습니다 . 그렇지 않으면 시간적 결합을 의미 합니다. 확실히 필드와 시간적 결합을 달성 할 수는 없지만, 일부 메서드가 실행될 때까지 필드만으로는 의미있는 동작을 전혀 발생시킬 수 없기 때문입니다.

    속성 접근 자는 작업이 수행되기 전에 개체를 유효한 상태로 강제 설정하여 특정 유형의 시간적 결합을 방지 하는 데 실제로 도움이 될 수 있습니다. 예를 들어 클래스에 a StartDateEndDate 다음 설정, EndDate(가) 이전에 StartDate강제 할 수 StartDate뿐만 아니라 다시. 이벤트 기반 사용자 인터페이스의 명백한 예를 포함하여 다중 스레드 또는 비동기 환경에서도 마찬가지입니다.

속성이 수행 할 수있는 기타 작업은 포함 할 수없는 필드입니다.

  • 지연 로딩 , 초기화 순서 오류를 방지하는 가장 효과적인 방법 중 하나입니다.
  • 변경 알림 .MVVM 아키텍처의 .
  • 상속 , 예를 들어 추상 정의Type 또는 Name파생 클래스 은 흥미롭지 만 그럼에도 불구하고 자신에 대한 지속적인 메타 데이터를 제공 할 수 있습니다.
  • 차단위의 덕분에 .
  • 인덱서 , COM interop으로 작업해야했던 모든 사람과 불가피한 Item(i)호출 분출은 멋진 것으로 인식 할 것입니다.
  • PropertyDescriptor로 작업 디자이너의 작성 및 일반적으로 XAML 프레임 워크 필수적이다.

Richter는 분명히 다작의 저자이며 CLR 및 C #에 대해 많이 알고 있지만, 그가 원래이 조언을 썼을 때처럼 보입니다. 그는 단지 오래된 습관을 버리고 싶지 않았고 C #의 관습을 받아들이는 데 어려움을 겪고있었습니다 (예 : C ++와 비교).

이것이 의미하는 바는 그의 “유해한 것으로 간주되는 속성”주장은 본질적으로 단일 진술로 요약됩니다. 속성은 필드처럼 보이지만 필드처럼 작동하지 않을 수도 있습니다. 그리고 성명서의 문제 는 사실이 아니 거나 기껏해야 오해의 소지가 있다는 것입니다. 속성 필드처럼 보이지 않습니다 . 적어도 필드처럼 보이지 않아야 합니다.

이 두 가지 매우 다른 CLR 언어에 의해 공유 유사한 규칙과 C #에서 강력한 코딩 규칙, 그리고 당신이 그들을 따르지 않는 경우의 FxCop은 비명합니다 :

  1. 필드한다 항상 개인 수 결코 공개.
  2. 필드는 camelCase로 선언해야합니다. 속성은 PascalCase입니다.

따라서 Foo.Bar = 42속성 접근 자인지 필드 접근 자 인지 에 대한 모호성이 없습니다 . 속성 접근 자이며 다른 메서드처럼 취급되어야합니다. 느릴 수 있고 예외가 발생할 수 있습니다. 이것이 추상화 의 특성입니다 . 반응하는 방법을 선언하는 클래스의 재량에 달려 있습니다. 클래스 디자이너는 최소한의 놀라움의 원칙을 적용해야하지만 호출자는 속성이 주석에 표시된 것을 제외하고는 어떤 것도 가정해서는 안됩니다. 그것은 의도적 인 것입니다.

속성의 대안은 모든 곳에서 getter / setter 메서드입니다. 이것이 Java 접근 방식이며 처음부터 논란 이되었습니다 . 그것이 당신의 가방이라면 괜찮지 만 우리가 .NET 진영에 들어가는 방식은 아닙니다. Fowler가 Syntactic Noise 라고 부르는 것을 피하기 위해 적어도 정적으로 형식화 된 시스템의 범위 내에서 시도 합니다. 우리는 추가 괄호, 추가 get/ set사마귀 또는 추가 메서드 서명을 원하지 않습니다. 명확성을 잃지 않고 피할 수있는 경우가 아닙니다.

당신이 좋아하는 것을 말하되, foo.Bar.Baz = quux.Answers[42]는 항상보다 읽기 쉬울 것 foo.getBar().setBaz(quux.getAnswers().getItem(42))입니다. 그리고 하루에 수천 줄을 읽을 때 차이가 있습니다.

(그리고 위 단락에 대한 당신의 자연스러운 반응이 “읽기 어렵지만 여러 줄로 나누면 더 쉬울 것”이라고 말하는 것이면 , 요점 을 완전히 놓쳤다 고 말하는 것이 유감입니다 .)


답변

일반적으로 속성을 사용하지 않아야하는 이유를 알 수 없습니다.

C # 3+의 자동 속성은 구문을 약간 단순화합니다.


답변

한 사람의 의견 일뿐입니다. 나는 꽤 많은 C # 책을 읽었고 “속성을 사용하지 말라”고 말하는 사람을 아직 보지 못했다.

개인적으로 속성은 C #의 가장 좋은 점 중 하나라고 생각합니다. 원하는 메커니즘을 통해 상태를 노출 할 수 있습니다. 무언가를 처음 사용할 때 느리게 인스턴스화 할 수 있으며 값 설정 등에 대한 유효성 검사를 수행 할 수 있습니다. 속성을 사용하고 작성할 때 속성을 훨씬 더 좋은 구문 인 setter와 getter로 생각합니다.

속성에 대한 경고는 몇 가지가 있습니다. 하나는 아마도 속성의 오용이고 다른 하나는 미묘 할 수 있습니다.

첫째, 속성은 메서드 유형입니다. 대부분의 클래스 사용자는 속성이 상당히 가벼울 것으로 기대하기 때문에 속성에 복잡한 논리를 배치하면 놀랍습니다.

public class WorkerUsingMethod
{
   // Explicitly obvious that calculation is being done here
   public int CalculateResult()
   {
      return ExpensiveLongRunningCalculation();
   }
}

public class WorkerUsingProperty
{
   // Not at all obvious.  Looks like it may just be returning a cached result.
   public int Result
   {
       get { return ExpensiveLongRunningCalculation(); }
   }
}

이러한 경우에 방법을 사용하면 구분하는 데 도움이됩니다.

둘째, 더 중요한 것은 디버깅하는 동안 속성을 평가하면 속성이 부작용을 일으킬 수 있다는 것입니다.

다음과 같은 속성이 있다고 가정합니다.

public int Result
{
   get
   {
       m_numberQueries++;
       return m_result;
   }
}

이제 너무 많은 쿼리가 작성 될 때 발생하는 예외가 있다고 가정하십시오. 디버깅을 시작하고 디버거에서 속성을 롤오버하면 어떤 일이 발생하는지 추측하십시오. 나쁜 것들. 이것을 피하십시오! 속성을 살펴보면 프로그램의 상태가 변경됩니다.

이것들은 내가 가진 유일한 경고입니다. 부동산의 이점이 문제보다 훨씬 크다고 생각합니다.


답변

그 이유는 매우 구체적인 맥락에서 주어 졌을 것입니다. 일반적으로 다른 방법입니다. 속성을 사용하면 클라이언트에 영향을주지 않고 클래스의 동작을 변경할 수있는 추상화 수준을 제공하므로 속성을 사용하는 것이 좋습니다.