IT

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

lottoking 2020. 6. 23. 07:01
반응형

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 #은 그러한 시나리오를 제한해서는 안됩니다. 적절하게 사용하는 것은 개발자의 책임입니다.


문제는 어떤 이유로 든 Microsoft가 읽기 전용, 쓰기 전용 및 읽기 / 쓰기의 세 가지 고유 한 유형의 속성이 있어야한다고 결정한 이유입니다.이 컨텍스트 중 하나만 지정된 컨텍스트에서 지정된 서명으로 존재할 수 있습니다. 속성은 동일하게 선언 된 속성으로 만 재정의 될 수 있습니다. 원하는 것을 수행하려면 이름과 서명이 같은 두 개의 속성을 만들어야합니다. 하나는 읽기 전용이고 다른 하나는 읽기 / 쓰기입니다.

개인적으로, "get"및 "set"메소드를 호출하는 구문 설탕으로 속성 -ish 구문을 사용할 수 있다는 점을 제외하고는 "properties"의 전체 개념을 폐지 할 수 있기를 바랍니다. 이것은 'add set'옵션을 용이하게 할뿐만 아니라 'get'이 'set'과 다른 유형을 반환하도록 허용합니다. 이러한 기능은별로 자주 사용되지 않지만 'get'메소드가 랩퍼 오브젝트를 리턴하는 반면 'set'은 랩퍼 또는 실제 데이터를 허용 할 수 있습니다.


Reflection을 사용하여이를 달성하기위한 해결 방법은 다음과 같습니다.

var UpdatedGiftItem = // object value to update;

foreach (var proInfo in UpdatedGiftItem.GetType().GetProperties())
{
    var updatedValue = proInfo.GetValue(UpdatedGiftItem, null);
    var targetpropInfo = this.GiftItem.GetType().GetProperty(proInfo.Name);
    targetpropInfo.SetValue(this.GiftItem, updatedValue,null);
}

이런 식으로 읽기 전용 인 속성에서 객체 값을 설정할 수 있습니다. 모든 시나리오에서 작동하지 않을 수 있습니다!


질문의 제목이 추상 속성을 재정의하는 것과 관련되거나 질문이 클래스의 get-only 속성을 재정의하는 것과 관련된 세부 사항으로 질문 제목을 변경해야합니다.


전자의 경우 (추상 속성을 재정의)

그 코드는 쓸모가 없습니다. 기본 클래스만으로는 Get-Only 속성 (아마도 인터페이스)을 재정의하도록 강요해서는 안됩니다. 기본 클래스는 구현 클래스의 특정 입력이 필요할 수있는 공통 기능을 제공합니다. 따라서 공통 기능은 추상 속성이나 메서드를 호출 할 수 있습니다. 주어진 경우 일반적인 기능 방법은 다음과 같은 추상 방법을 재정의하도록 요구해야합니다.

public int GetBar(){}

그러나 당신이 그것을 제어 할 수 없으며 기본 클래스의 기능이 자체 공용 속성 (이상)을 읽는다면 다음과 같이하십시오.

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

public class ConcreteClass : BaseClass
{
    private int _bar;
    public override int Bar
    {
        get { return _bar; }
    }
    public void SetBar(int value)
    {
        _bar = value;
    }
}

나는 (이상한) 의견을 지적하고 싶습니다 : 모범 사례는 클래스가 자체 공용 속성을 사용하지 않고 개인 / 보호 필드가있을 때 사용하는 것입니다. 따라서 이것은 더 나은 패턴입니다.

public abstract class BaseClass {
    protected int _bar;
    public int Bar { get { return _bar; } }
    protected void DoBaseStuff()
    {
        SetBar();
        //Do something with _bar;
    }
    protected abstract void SetBar();
}

public class ConcreteClass : BaseClass {
    protected override void SetBar() { _bar = 5; }
}

후자의 경우 (클래스의 get-only 속성을 재정의)

모든 비추 상 속성에는 세터가 있습니다. 그렇지 않으면 쓸모가 없으므로 사용하지 않아도됩니다. Microsoft는 귀하가 원하는 것을 수행하도록 허용하지 않습니다. 이유 : 세터는 어떤 형태 나 다른 형태로 존재하며 원하는 Veerryy를 쉽게 달성 할 수 있습니다.

기본 클래스, 또는 당신이 가진 속성을 읽을 수있는 모든 클래스 {get;},이 일부 해당 속성에 대한 노출 세터의 종류. 메타 데이터는 다음과 같습니다.

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

그러나 구현에는 복잡한 스펙트럼의 두 가지 끝이 있습니다.

최소 복합물 :

public abstract class BaseClass
{
    private int _bar;
    public int Bar { 
        get{
            return _bar;
        }}
    public void SetBar(int value) { _bar = value; }
}

가장 복잡한 :

public abstract class BaseClass
{
    private int _foo;
    private int _baz;
    private int _wtf;
    private int _kthx;
    private int _lawl;

    public int Bar
    {
        get { return _foo * _baz + _kthx; }
    }
    public bool TryDoSomethingBaz(MyEnum whatever, int input)
    {
        switch (whatever)
        {
            case MyEnum.lol:
                _baz = _lawl + input;
                return true;
            case MyEnum.wtf:
                _baz = _wtf * input;
                break;
        }
        return false;
    }
    public void TryBlowThingsUp(DateTime when)
    {
        //Some Crazy Madeup Code
        _kthx = DaysSinceEaster(when);
    }
    public int DaysSinceEaster(DateTime when)
    {
        return 2; //<-- calculations
    }
}
public enum MyEnum
{
    lol,
    wtf,
}

내 요점은 어느 쪽이든, 당신은 세터를 노출시켰다. 귀하의 경우, int Bar기본 클래스가 처리하지 않기를 원하거나, 처리 방법을 검토 할 수있는 권한이 없거나, 어떤 코드를 당신의 의지에 비해 실제로 신속하게 처리 해야하는 작업을 수행하지 않았기 때문에 재정의 할 수 있습니다 .

후기와 전 (결론)

Long-Story Short : Microsoft는 아무것도 변경할 필요가 없습니다. 구현 클래스를 설정하는 방법을 선택할 수 있으며 생성자를 산정하면 기본 클래스를 모두 사용하거나 전혀 사용하지 않을 수 있습니다.


Solution for only a small subset of use cases, but nevertheless: in C# 6.0 "readonly" setter is automatically added for overridden getter-only properties.

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

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

    public ConcreteClass(int bar)
    {
        Bar = bar;
    }
}

Because at the IL level, a read/write property translates into two (getter and setter) methods.

When overriding, you have to keep supporting the underlying interface. If you could add a setter, you would effectively be adding a new method, which would remain invisible to the outside world, as far as your classes' interface was concerned.

True, adding a new method would not be breaking compatibility per se, but since it would remain hidden, decision to disallow this makes perfect sense.


Because that would break the concept of encapsulation and implementation hiding. Consider the case when you create a class, ship it, and then the consumer of your class makes himself able to set a property for which you originally provide a getter only. It would effectively disrupt any invariants of your class which you can depend on in your implementation.


Because a class that has a read-only property (no setter) probably has a good reason for it. There might not be any underlying datastore, for example. Allowing you to create a setter breaks the contract set forth by the class. It's just bad OOP.


A read-only property in the base class indicates that this property represents a value that can always be determined from within the class (for example an enum value matching the (db-)context of an object). So the responsibillity of determining the value stays within the class.

Adding a setter would cause an awkward issue here: A validation error should occur if you set the value to anything else than the single possible value it already has.

Rules often have exceptions, though. It is very well possible that for example in one derived class the context narrows the possible enum values down to 3 out of 10, yet the user of this object still needs to decide which one is correct. The derived class needs to delegate the responsibillity of determining the value to the user of this object. Important to realize is that the user of this object should be well aware of this exception and assume the responsibillity to set the correct value.

My solution in these kind of situations would be to leave the property read-only and add a new read-write property to the derived class to support the exception. The override of the original property will simply return the value of the new property. The new property can have a proper name indicating the context of this exception properly.

This also supports the valid remark: "make it as hard as possible for misunderstandings to crop up" by Gishu.


This is not impossible. You simply have to use the "new" keyword in your property. For example,

namespace {
    public class Base {
        private int _baseProperty = 0;

        public virtual int BaseProperty {
            get {
                return _baseProperty;
            }
        }

    }

    public class Test : Base {
        private int _testBaseProperty = 5;

        public new int BaseProperty {
            get {
                return _testBaseProperty;
            }
            set {
                _testBaseProperty = value;
            }
        }
    }
}

It appears as if this approach satisfies both sides of this discussion. Using "new" breaks the contract between the base class implementation and the subclass implementation. This is necessary when a Class can have multiple contracts (either via interface or base class).

Hope this helps

참고URL : https://stackoverflow.com/questions/82437/why-is-it-impossible-to-override-a-getter-only-property-and-add-a-setter

반응형