getter 전용 속성을 재정의하고 setter를 추가 할 수없는 이유는 무엇입니까? [닫은]

다음 C # 코드가 허용되지 않는 이유 :

public abstract class BaseClass
{
    public abstract int Bar { get;}
}

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get { return 0; }
        set {}
    }
}

CS0546 ‘ConcreteClass.Bar.set’: ‘BaseClass.Bar’에 재정의 가능한 set 접근자가 없기 때문에 재정의 할 수 없습니다.



답변

Baseclass의 작성자는 Bar가 읽기 전용 특성이어야한다고 명시 적으로 선언했기 때문입니다. 파생이이 계약을 위반하고 읽기 / 쓰기로 만드는 것은 의미가 없습니다.

나는 이것에 대해 Microsoft와 함께 있습니다.
베이스 클래스 파생에 대해 코드를 작성하라는 새로운 프로그래머라고 가정 해 봅시다. Bar가 쓸 수 없다고 가정하는 무언가를 작성하십시오 (Baseclass가 명시 적으로 get only 속성이라고 명시하기 때문에). 이제 당신의 파생으로 내 코드가 깨질 수 있습니다. 예 :

public class BarProvider
{ BaseClass _source;
  Bar _currentBar;

  public void setSource(BaseClass b)
  {
    _source = b;
    _currentBar = b.Bar;
  }

  public Bar getBar()
  { return _currentBar;  }
}

Bar는 BaseClass 인터페이스에 따라 설정할 수 없으므로 BarProvider는 캐싱이 안전한 것으로 가정합니다. Bar는 수정할 수 없기 때문입니다. 그러나 파생에서 설정이 가능한 경우 누군가가 _source 객체의 Bar 속성을 외부에서 수정 한 경우이 클래스는 오래된 값을 제공 할 수 있습니다. 요점은 ‘ 개방하라, 비열한 일이나 사람들을 놀라게하지 말라

업데이트 : Ilya Ryzhenkov가 ‘왜 인터페이스가 같은 규칙으로 재생되지 않습니까?’
흠 .. 내가 생각할 때 더 무거워진다.
인터페이스는 ‘Bar라는 이름의 읽기 속성을 구현할 것으로 예상됩니다’라는 계약입니다. 개인적 으로 인터페이스를 본다면 읽기 전용이라고 가정 할 가능성이 훨씬 적습니다. 인터페이스에서 get-only 속성을 볼 때 기본 클래스에서 ‘모든 구현 이이 속성을 표시합니다’로 읽습니다 .’Bar is a read-only property ‘로 클릭합니다. 물론 기술적으로 계약을 위반하지는 않습니다. 더 많은 일을하고 있습니다. 그래서 당신은 어떤 의미에서 옳습니다. 나는 ‘오해가 자랄 수 있도록 가능한 한 어렵게 만드십시오’라고 말함으로써 가깝습니다.


답변

주된 이유는 구문이 다른 방식으로 작동하기에 너무 명시 적이라고 생각합니다. 이 코드는 :

public override int MyProperty { get { ... } set { ... } }

get과 및 모두 set가 재정의 됨을 매우 분명합니다 . 더 없다 set컴파일러는 불평 있도록 기본 클래스에서. 기본 클래스에 정의되어 있지 않은 메서드를 재정의 할 수없는 것처럼 setter도 재정의 할 수 없습니다.

컴파일러가 의도를 추측하고 재정의 할 수있는 메소드 (이 경우 게터)에만 재정의를 적용해야한다고 말할 수도 있지만 C # 디자인 원칙 중 하나에 위배됩니다. 컴파일러는 의도를 추측해서는 안됩니다 모르게 잘못 추측 할 수 있기 때문입니다.

에릭 리퍼 (Eric Lippert)가 말한 것처럼 다음과 같은 구문은 훌륭하게 작동 할 수 있습니다.

public int MyProperty
{
    override get { ... }
    set { ... }
}

또는 자동 구현 된 속성의 경우

public int MyProperty { override get; set; }

답변

나는 오늘도 똑같은 문제를 우연히 발견했으며 이것을 원할만한 타당한 이유가 있다고 생각합니다.

먼저 get-only 속성이 반드시 읽기 전용으로 변환되는 것은 아니라고 주장하고 싶습니다. “이 인터페이스 / 추상에서이 값을 얻을 수 있습니다”로 해석합니다. 이는 해당 인터페이스 / 추상 클래스의 일부 구현에서이 값을 명시 적으로 설정하기 위해 사용자 / 프로그램이 필요하지 않음을 의미하지 않습니다. 추상 클래스는 필요한 기능의 일부를 구현하는 목적을 제공합니다. 상속 된 클래스가 계약을 위반하지 않고 세터를 추가 할 수없는 이유는 전혀 없습니다.

다음은 오늘 필요한 것의 간단한 예입니다. 나는이 문제를 해결하기 위해 인터페이스에 세터를 추가해야했습니다. SetProp 메소드를 추가하지 않고 setter를 추가하지 않는 이유는 Prop의 직렬화에 인터페이스의 특정 구현이 DataContract / DataMember를 사용했기 때문에 목적을 위해 다른 속성을 추가 해야하는 경우 불필요하게 복잡해 졌기 때문입니다. 직렬화

interface ITest
{
    // Other stuff
    string Prop { get; }
}

// Implements other stuff
abstract class ATest : ITest
{
    abstract public string Prop { get; }
}

// This implementation of ITest needs the user to set the value of Prop
class BTest : ATest
{
    string foo = "BTest";
    public override string Prop
    {
        get { return foo; }
        set { foo = value; } // Not allowed. 'BTest.Prop.set': cannot override because 'ATest.Prop' does not have an overridable set accessor
    }
}

// This implementation of ITest generates the value for Prop itself
class CTest : ATest
{
    string foo = "CTest";
    public override string Prop
    {
        get { return foo; }
        // set; // Not needed
    }
}

나는 이것이 단지 “나의 2 센트”의 게시물이라는 것을 알고 있지만, 나는 원래의 포스터와 함께 느끼고 이것이 좋은 것임을 합리화하려고 노력합니다. 특히 이상한 것을 직접 상속 할 때 동일한 제한이 적용되지 않는다는 점을 고려하면 상호 작용.

또한 재정의 대신 new를 사용하는 것에 대한 언급은 여기에 적용되지 않으며 단순히 작동하지 않으며 인터페이스를 통해 설명 된 가상 게터와 같은 원하는 결과를 얻지 못합니다.


답변

있을 수있다

TL; DR 당신이 원한다면 당신은 세터와 GET-유일한 방법을 재정의 할 수 있습니다. 기본적으로는 다음과 같습니다.

  1. 같은 이름을 사용하여 newa get와 a set를 모두 가진 속성을 만듭니다 .

  2. 다른 작업을 수행하지 않으면 get파생 클래스가 기본 유형을 통해 호출 될 때 이전 메소드가 계속 호출됩니다. 이 문제를 해결하려면 이전 메소드에서 abstract사용 하는 중간 계층을 추가 하여 새 메소드의 결과 를 강제로 리턴하십시오 .overridegetget

이를 통해 기본 정의에 속성 이없는 경우에도 get/로 속성을 재정의 할 수 있습니다 set.

보너스로 원하는 경우 반환 유형을 변경할 수도 있습니다.

  • 기본 정의가 get-only 인 경우 더 파생 된 리턴 유형을 사용할 수 있습니다.

  • 기본 정의가 set-only 인 경우 덜 파생 된 반환 유형을 사용할 수 있습니다.

  • 기본 정의가 이미 get/ set이면 다음을 수행하십시오.

    • 당신은 더 파생 반환 형식을 사용할 수있는 경우에 당신이 그것을 만들 set오닐;

    • 당신은 덜 파생 반환 형식을 사용할 수있는 경우에 당신이 그것을 만들 get오닐.

모든 경우에 원하는 경우 동일한 반환 유형을 유지할 수 있습니다. 아래 예제는 단순화를 위해 동일한 리턴 유형을 사용합니다.

상황 : 기존 get전용 속성

수정할 수없는 클래스 구조가 있습니다. 아마도 하나의 클래스 일 수도 있고 기존 상속 트리 일 수도 있습니다. 어떤 경우이든 set속성에 메서드를 추가하려고 하지만 할 수는 없습니다.

public abstract class A                     // Pre-existing class; can't modify
{
    public abstract int X { get; }          // You want a setter, but can't add it.
}
public class B : A                          // Pre-existing class; can't modify
{
    public override int X { get { return 0; } }
}

문제 : 할 수 없습니다 – 단지와 /overridegetgetset

당신이 원하는 overrideA를 get/ set부동산,하지만 컴파일되지 않습니다.

public class C : B
{
    private int _x;
    public override int X
    {
        get { return _x; }
        set { _x = value; }   //  Won't compile
    }
}

솔루션 : abstract중간 레이어 사용

당신이 할 수있는 직접적 동안 overrideA를 get/ set재산, 당신은 할 수 있습니다 :

  1. 같은 이름 으로 new get/ set속성을 만듭니다 .

  2. override이전 get새에 대한 접근과 방법 get방법은 일관성을 보장합니다.

따라서 먼저 abstract중간 계층 을 작성하십시오 .

public abstract class C : B
{
    //  Seal off the old getter.  From now on, its only job
    //  is to alias the new getter in the base classes.
    public sealed override int X { get { return this.XGetter; }  }
    protected abstract int XGetter { get; }
}

그런 다음 이전에 컴파일하지 않은 클래스를 작성하십시오. 실제로 overrideget-only 속성을 사용 하지 않기 때문에 이번에는 컴파일 됩니다. 대신 new키워드를 사용하여 교체하고 있습니다.

public class D : C
{
    private int _x;
    public new virtual int X { get { return this._x; } set { this._x = value; } }

    //  Ensure base classes (A,B,C) use the new get method.
    protected sealed override int XGetter { get { return this.X; } }
}

결과 : 모든 것이 작동합니다!

분명히 이것은 의도 한대로 작동합니다 D.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

test.X = 7;
Print(test.X);      // Prints "7", as intended.

D기본 클래스 중 하나로 볼 때 모든 것이 여전히 의도 한대로 작동합니다 ( 예 : A또는) B. 그러나 그것이 작동하는 이유는 조금 덜 분명 할 수 있습니다.

var test = new D() as B;
//test.X = 7;       // This won't compile, because test looks like a B,
                    // and B still doesn't provide a visible setter.

그러나의 기본 클래스 정의 get는 여전히 파생 클래스의 정의에 의해 재정의 get되므로 여전히 완전히 일관성이 있습니다.

var test = new D();
Print(test.X);      // Prints "0", the default value of an int.

var baseTest = test as A;
Print(test.X);      // Prints "7", as intended.

토론

이 방법을 사용하면 속성 setget전용 속성 에 추가 할 수 있습니다 . 그것을 사용하여 다음과 같은 작업을 수행 할 수도 있습니다.

  1. 에 어떤 속성을 변경 get– 단지, set오닐, 또는 get– 및 – set관계없이 기본 클래스에 무엇의 속성입니다.

  2. 파생 클래스에서 메소드의 리턴 유형을 변경하십시오.

주요 단점은 더 많은 코딩이 있고 abstract class상속 트리에 추가가 있다는 것 입니다. 매개 변수를 사용하는 생성자는 중간 계층에서 복사 / 붙여 넣기해야하기 때문에 약간 성 가실 수 있습니다.


답변

파생 형식에서 getter를 재정의 할 수없는 것이 안티 패턴이라는 데 동의합니다. Read-Only는 순수한 기능 계약 (최고의 투표 답변으로 암시 됨)이 아닌 구현 부족을 지정합니다.

동일한 오해가 촉진되었거나 문법 단순화로 인해 Microsoft에 이러한 제한이 있다고 생각합니다. 그러나 이제 범위를 적용하여 개별적으로 가져 오거나 설정할 수 있습니다. 아마도 재정의도 가능할 수 있기를 바랍니다.

최고 투표 답변에 따르면 오해는 읽기 전용 속성이 읽기 / 쓰기 속성보다 “순수”해야한다는 것입니다. 프레임 워크에서 많은 일반적인 읽기 전용 속성을 살펴보십시오. 값은 상수 / 순수하게 기능하지 않습니다. 예를 들어 DateTime.Now는 읽기 전용이지만 순수한 기능 값을 제외한 모든 것입니다. 다음 번에 같은 값을 반환한다고 가정하면 읽기 전용 속성의 값을 ‘캐시’하는 것은 위험합니다.

어쨌든, 나는이 한계를 극복하기 위해 다음 전략 중 하나를 사용했습니다. 둘 다 완벽하지는 않지만이 언어 부족을 뛰어 넘을 수 있습니다.

   class BaseType
   {
      public virtual T LastRequest { get {...} }
   }

   class DerivedTypeStrategy1
   {
      /// get or set the value returned by the LastRequest property.
      public bool T LastRequestValue { get; set; }

      public override T LastRequest { get { return LastRequestValue; } }
   }

   class DerivedTypeStrategy2
   {
      /// set the value returned by the LastRequest property.
      public bool SetLastRequest( T value ) { this._x = value; }

      public override T LastRequest { get { return _x; } }

      private bool _x;
   }

답변

새 속성을 만들어 문제를 해결할 수도 있습니다.

public new int Bar
{
    get { return 0; }
    set {}
}

int IBase.Bar {
  get { return Bar; }
}

답변

모든 요점을 이해할 수는 있지만 실제로 C # 3.0의 자동 속성은 쓸모가 없습니다.

당신은 그런 식으로 할 수 없습니다 :

public class ConcreteClass : BaseClass
{
    public override int Bar
    {
        get;
        private set;
    }
}

IMO, C #은 그러한 시나리오를 제한해서는 안됩니다. 적절하게 사용하는 것은 개발자의 책임입니다.