왜 정적 클래스를 상속받을 수 없습니까?


224

실제로 어떤 상태가 필요하지 않은 여러 클래스가 있습니다. 조직의 관점에서, 나는 그것들을 계층 구조에 넣고 싶습니다.

그러나 정적 클래스에 대한 상속을 선언 할 수없는 것 같습니다.

그런 것 :

public static class Base
{
}

public static class Inherited : Base
{
}

작동 안 할 것이다.

왜 언어 디자이너들이 그 가능성을 닫았습니까?


답변:


174

여기 에서 인용 :

이것은 실제로 의도적으로 설계된 것입니다. 정적 클래스를 상속받을 이유가없는 것 같습니다. 클래스 이름 자체를 통해 항상 액세스 할 수있는 공개 정적 멤버가 있습니다. 정적 인 물건을 상속받는 것으로 본 유일한 이유는 입력하는 두 문자를 저장하는 것과 같은 나쁜 이유였습니다.

정적 멤버를 직접 범위로 가져 오는 메커니즘을 고려해야하는 이유가있을 수 있지만 (Orcas 제품주기 이후에이를 실제로 고려할 것임) 정적 클래스 상속은가는 방법이 아닙니다. 사용하는 잘못된 메커니즘이며 작동합니다. 정적 클래스에있는 정적 멤버에만 해당됩니다.

(Mads Torgersen, C # 언어 PM)

channel9의 다른 의견

.NET의 상속은 인스턴스 기반에서만 작동합니다. 정적 메소드는 인스턴스 레벨이 아닌 유형 레벨에서 정의됩니다. 정적 메서드 / 속성 / 이벤트에서는 재정의가 작동하지 않는 이유입니다 ...

정적 메소드는 메모리에서 한 번만 보유됩니다. 작성된 가상 테이블 등이 없습니다.

.NET에서 인스턴스 메소드를 호출하면 항상 현재 인스턴스를 제공합니다. 이것은 .NET 런타임에 숨겨져 있지만 발생합니다. 각 인스턴스 메소드에는 첫 번째 인수로 메소드가 실행되는 오브젝트에 대한 포인터 (참조)가 있습니다. 정적 메서드에서는 발생하지 않습니다 (유형 수준에서 정의되었으므로). 컴파일러는 호출 할 메소드를 어떻게 선택해야합니까?

(littleguru)

그리고 귀중한 아이디어로서 littleguru 는이 문제에 대한 부분적인 "해결책": Singleton 패턴을 가지고 있습니다.


92
Torgersen 측의 상상력이 부족합니다. 나는 이것을하고 싶은 꽤 좋은 이유가 있었다 :)
Thorarin

10
이건 어때요? 오픈 소스가 아닌 dll이 있거나 소스에 액세스 할 수없는 경우 NUnit이라고 가정 해 봅시다. Assert 클래스를 확장하여 Throws <> (Action a)와 같은 메소드를 갖기를 원합니다. 클래스를 입력 한 다음 Throws 메소드를 추가하십시오. 문제 해결됨. 코드에서 사용하는 것이 더 깨끗합니다 ... 다른 클래스 이름을 생각해 내고 클래스 이름을 기억하는 대신 Assert를 입력하십시오. 사용 가능한 방법을 참조하십시오.
user420667

4
@KonradMorawski 정적 클래스에 대한 확장 메소드를 작성하는 것은 불가능합니다. stackoverflow.com/questions/249222/…
마틴 브라운

2
@modiX 물론입니다. 여기서 오해 했어 필자가 의미 한 바는 user420667에 의해 설명 된 시나리오에서 필요한 기능 이 정적 클래스에 대한 확장 메소드를 허용하는 것만 큼 적게 제공 될 수 있다는 입니다. 정적 상속이 필요하지 않습니다. 그렇기 때문에 그의 시나리오가 정적 상속을 도입하는 데 아주 좋은 근거가되지 않았습니다. 이 측면에서 C #을 수정해야한다면, 사소한 것이 실용적 일 때 큰 재 설계가 필요한 이유는 무엇입니까?
Konrad Morawski

5
@Learner 정말 공평하지 않습니다. .Net에서는 모든 것이에서 상속 object되며 모든 사람이이를 알고 있어야합니다. 따라서 정적 클래스 object 명시 적으로 지정 여부에 관계없이 항상에서 상속됩니다 .
RenniePet

71

정적 클래스를 상속 할 수없는 주된 이유는 클래스가 추상적이고 봉인되어 있기 때문입니다 (이로 인해 해당 클래스의 인스턴스가 생성되지 않음).

그래서 이거:

static class Foo { }

이 IL로 컴파일합니다.

.class private abstract auto ansi sealed beforefieldinit Foo
  extends [mscorlib]System.Object
 {
 }

17
정적은 추상 + 봉인으로 구현되었다고 말하고 있습니다. 그는 이런 일이 있었 는지 알고 싶어합니다 . 왜 봉인되어 있습니까?
Lucas

22
이것은 모든 질문에 답하지는 않습니다. 단지 그가 요구 한 문제를 해결합니다.
Igby Largeman

28
OP는 "정적 클래스를 상속 할 수없는 이유"가 아니라 "정적 클래스가 왜 봉인되어 있습니까?"라고 물어야합니다. 대답은 물론 "봉인되어 있기 때문에" 입니다. 이 답변은 내 투표를 얻습니다.
Alex Budovski

6
정적 클래스가 봉인 클래스로 자동 컴파일된다는 사실을 공유해 주셔서 감사합니다. 어떻게 발생했는지 궁금했습니다.
Mark

23
@AlexBudovski :이 대답은 옳은 것이지만 잃어버린 사람에게 "내가 어디에 있는지"물어 보면 "나의 앞에있다"고 말하는 것처럼 쓸모가 없습니다.
DeepSpace101

24

이런 식으로 생각하십시오 : 다음과 같이 유형 이름을 통해 정적 멤버에 액세스하십시오.

MyStaticType.MyStaticMember();

해당 클래스에서 상속받은 경우 새 유형 이름을 통해 클래스에 액세스해야합니다.

MyNewType.MyStaticMember();

따라서 새 항목은 코드에서 사용될 때 원본과 관계가 없습니다. 다형성과 같은 것들에 대한 상속 관계를 이용할 수있는 방법은 없습니다.

아마도 당신은 원래 수업에서 일부 항목을 확장하고 싶다고 생각하고 있습니다. 이 경우 원본의 멤버를 완전히 새로운 유형으로 사용하는 것을 막을 수있는 것은 없습니다.

기존 정적 유형에 메소드를 추가하려고 할 수 있습니다. 확장 방법을 통해 이미 그렇게 할 수 있습니다.

아마도 Type런타임에 함수에 정적을 전달 하고 메서드의 기능을 정확히 몰라도 해당 유형의 메서드를 호출 하려고 할 수 있습니다 . 이 경우 인터페이스를 사용할 수 있습니다.

따라서 결국 정적 클래스 상속을 통해 아무것도 얻지 못합니다.


5
확장 메소드를 통해 기존 정적 유형에 메소드를 추가 할 수 없습니다. 원하는 사용 예를 보려면 허용 된 답변에 대한 내 의견을 참조하십시오. (기본적으로 MyNewType이라는 이름을 MyStaticType으로 바꾸려면 MyStaticType : OldProject.MyStaticType)
user420667

2
하나는이 방식으로 정적 유형과 가상 정적 회원들과 상속 관계를 정의 할 수 SomeClass<T> where T:Foo의 멤버에 액세스 할 수 Foo및 경우 T일부 가상 정적 멤버 오버라이드 클래스했다 Foo, 일반 클래스가 그 재정의를 사용하는 것입니다. 현재 CLR과 호환되는 방식으로 언어가 수행 할 수있는 규칙을 정의하는 것이 가능할 수도 있습니다 (예 : 해당 멤버가있는 클래스는 정적 필드와 함께 해당 인스턴스 멤버가 포함 된 보호 된 비 정적 클래스를 정의해야 함). 해당 유형의 인스턴스를 보유).
supercat

정적 클래스를 상속받는 것이 옳은 일이라고 생각하는 경우가 있었지만 분명히 액세스 할 수는 있습니다. 원시 데이터 스트림을 프린터로 전송하기위한 정적 클래스가 있습니다 (산업용 라벨 프린터와 통신하기 위해). Windows API 선언이 많이 있습니다. 이제 동일한 API 호출이 필요한 프린터를 망칠 다른 클래스를 작성하고 있습니다. 결합시키다? 안좋다. 두 번 선언 하시겠습니까? 추한. 상속? 양호하지만 허용되지 않습니다.
Loren Pechtel

3

클래스 계층 구조를 사용하여 달성하려는 것은 단순히 네임 스페이스를 통해 달성 할 수 있습니다. 따라서 C #과 같은 네임 스페이스를 지원하는 언어는 정적 클래스의 클래스 계층 구조를 구현할 필요가 없습니다. 클래스를 인스턴스화 할 수 없으므로 네임 스페이스를 사용하여 얻을 수있는 클래스 정의의 계층 구조 만 있으면됩니다.


클래스를 유형으로받는 일반 로더가 있습니다. 이 클래스에는 5 개의 도우미 메서드와 문자열 (이름 및 설명)을 반환하는 2 개의 메서드가 있습니다. 나는 그것을 잘못 선택했을 수도 있지만 (그렇다고 생각한다), 내가 찾은 유일한 해결책은 일반 로더에서 클래스를 인스턴스화하고 메소드에 액세스하는 것입니다 ...
ANeves

대안은 거대한 사전 일 것입니다. 또한 "무엇을 달성하고 싶은지"라고 말할 때는 대신 "목표로하는 것"또는 이와 유사한 것을 말해야합니다.
ANeves

3

대신 컴포지션을 사용할 수 있습니다. 그러면 정적 유형에서 클래스 객체에 액세스 할 수 있습니다. 그러나 여전히 인터페이스 또는 추상 클래스를 구현할 수 없습니다


2

상속 된 클래스 이름을 통해 "상속 된"정적 멤버에 액세스 할 수 있지만 정적 멤버는 실제로 상속되지는 않습니다. 이것이 부분적으로 가상이거나 추상적 일 수없고 재정의 될 수없는 이유입니다. 예제에서 Base.Method ()를 선언 한 경우 컴파일러는 Inherited.Method ()에 대한 호출을 Base.Method ()에 다시 매핑합니다. Base.Method ()를 명시 적으로 호출 할 수도 있습니다. 작은 테스트를 작성하고 리플렉터로 결과를 볼 수 있습니다.

정적 멤버를 상속 할 수없고 정적 클래스에 정적 멤버 포함 할 수 있다면 정적 클래스를 상속하면 어떤 이점이 있습니까?


1

흠 ... 정적 메소드로 채워진 비 정적 클래스가 있다면 훨씬 다를 것입니다 ..?


2
그것이 저에게 남겨진 것입니다. 나는 이렇게한다. 그리고 나는 그것을 좋아하지 않습니다.
사용자

이 방법으로도 수행했지만 어떤 이유로 든 객체를 인스턴스화하지 않을 것이라는 것을 알면 미적 감각이 없습니다. 나는 재료비가 없다는 사실에도 불구하고 항상 이와 같은 세부 사항에 대해 고민합니다.
gdbj

정적 방법 가득 비 정적 클래스에서는 확장 방법 : 넣을 수 없습니다
야쿱 Szułakiewicz

1

해결 방법은 정적 클래스를 사용하지 않고 생성자를 숨기므로 클래스 정적 멤버 만 클래스 외부에서 액세스 할 수있는 것입니다. 결과는 상속 가능한 "정적"클래스입니다.

public class TestClass<T>
{
    protected TestClass()
    { }

    public static T Add(T x, T y)
    {
        return (dynamic)x + (dynamic)y;
    }
}

public class TestClass : TestClass<double>
{
    // Inherited classes will also need to have protected constructors to prevent people from creating instances of them.
    protected TestClass()
    { }
}

TestClass.Add(3.0, 4.0)
TestClass<int>.Add(3, 4)

// Creating a class instance is not allowed because the constructors are inaccessible.
// new TestClass();
// new TestClass<int>();

불행히도 "설계 상"언어 제한으로 인해 다음과 같은 작업을 수행 할 수 없습니다.

public static class TestClass<T>
{
    public static T Add(T x, T y)
    {
        return (dynamic)x + (dynamic)y;
    }
}

public static class TestClass : TestClass<double>
{
}

1

정적 상속처럼 보이는 것을 할 수 있습니다.

트릭은 다음과 같습니다.

public abstract class StaticBase<TSuccessor>
    where TSuccessor : StaticBase<TSuccessor>, new()
{
    protected static readonly TSuccessor Instance = new TSuccessor();
}

그런 다음이 작업을 수행 할 수 있습니다

public class Base : StaticBase<Base>
{
    public Base()
    {
    }

    public void MethodA()
    {
    }
}

public class Inherited : Base
{
    private Inherited()
    {
    }

    public new static void MethodA()
    {
        Instance.MethodA();
    }
}

Inherited클래스 자체 정적 아니지만, 우리는 그것을 만들 수 없습니다. 실제로 빌드하는 정적 생성자와 상속 가능한 Base모든 속성 및 메서드를 상속했습니다 Base. 이제 정적 컨텍스트에 노출 해야하는 각 메소드 및 속성에 대해 정적 래퍼를 만드는 것은 남았습니다.

정적 랩퍼 메소드 및 new키워드 를 수동으로 작성해야하는 것과 같은 단점이 있습니다 . 그러나이 방법은 정적 상속과 실제로 유사한 것을 지원하는 데 도움이됩니다.

추신 : 우리는 이것을 컴파일 된 쿼리를 만드는 데 사용했으며 실제로는 ConcurrentDictionary로 대체 할 수 있지만 스레드 안전성이있는 정적 읽기 전용 필드로는 충분했습니다.


1

내 대답 : 열악한 디자인 선택. ;-)

이것은 구문 영향에 초점을 맞춘 흥미로운 토론입니다. 필자의 주장의 핵심은 디자인 결정으로 인해 정적 클래스가 봉인되었다는 것이다. 정적 클래스 이름의 투명성에 중점을 두어 자식 이름 뒤에 숨기지 않고 ( '혼란') 최상위 레벨에 나타 납니까? 베이스 나 자식에 직접 액세스 할 수있는 언어 구현을 혼란스럽게 만들 수 있습니다.

정적 상속을 가정 한 의사 예제는 어떤 식 으로든 정의되었습니다.

public static class MyStaticBase
{
    SomeType AttributeBase;
}

public static class MyStaticChild : MyStaticBase
{
    SomeType AttributeChild;
}

다음으로 이어질 것입니다 :

 // ...
 DoSomethingTo(MyStaticBase.AttributeBase);
// ...

어느 스토리지와 동일한 스토리지에 영향을 줄 수 있습니까?

// ...
DoSomethingTo(MyStaticChild.AttributeBase);
// ...

매우 혼란 스럽다!

하지만 기다려! 컴파일러는 둘 다에 동일한 서명이 정의 된 MyStaticBase 및 MyStaticChild를 어떻게 처리합니까? 위의 예보다 자식이 재정의하면 동일한 저장소를 변경하지 않을 수 있습니까? 이것은 훨씬 더 혼란을 초래합니다.

제한된 정적 상속에 대한 강력한 정보 공간 타당성이 있다고 생각합니다. 곧 한계에 대한 자세한 내용. 이 의사 코드는 다음 값을 보여줍니다.

public static class MyStaticBase<T>
{
   public static T Payload;
   public static void Load(StorageSpecs);
   public static void Save(StorageSpecs);
   public static SomeType AttributeBase
   public static SomeType MethodBase(){/*...*/};
}

그럼 당신은 얻을 :

public static class MyStaticChild : MyStaticBase<MyChildPlayloadType>
{
   public static SomeType AttributeChild;
   public static SomeType SomeChildMethod(){/*...*/};
   // No need to create the PlayLoad, Load(), and Save().
   // You, 'should' be prevented from creating them, more on this in a sec...
} 

사용법은 다음과 같습니다.

// ...
MyStaticChild.Load(FileNamePath);
MyStaticChild.Save(FileNamePath);
doSomeThing(MyStaticChild.Payload.Attribute);
doSomething(MyStaticChild.AttributeBase);
doSomeThing(MyStaticChild.AttributeChild);
// ...

정적 하위를 작성하는 사람은 플랫폼 또는 환경의 직렬화 엔진에 적용될 수있는 제한 사항을 이해하는 한 직렬화 프로세스에 대해 생각할 필요가 없습니다.

정적 (싱글 톤 및 다른 형태의 '글로벌')은 종종 구성 저장소 주위에 나타납니다. 정적 상속은 이러한 종류의 책임 할당이 구성 계층 구조와 일치하도록 구문으로 명확하게 표현 될 수 있도록합니다. 내가 보여 주었 듯이, 기본 정적 상속 개념이 구현되면 엄청난 모호성이 생길 수 있습니다.

올바른 디자인 선택은 특정 제한을 가진 정적 상속을 허용하는 것이라고 생각합니다.

  1. 아무것도 무시하지 않습니다. 자식은 기본 속성, 필드 또는 메소드를 대체 할 수 없습니다. 컴파일러에서 자식과 기본을 정렬 할 수 있도록 서명에 차이가있는 한 오버로드는 정상입니다.
  2. 일반 정적베이스 만 허용하며, 일반이 아닌 정적베이스에서는 상속 할 수 없습니다.

일반 참조를 통해 여전히 동일한 상점을 변경할 수 있습니다 MyStaticBase<ChildPayload>.SomeBaseField. 그러나 제네릭 형식을 지정해야하므로 권장하지 않습니다. 자식 참조는 더 깨끗하지만 : MyStaticChild.SomeBaseField.

나는 컴파일러 작성자가 아니기 때문에 컴파일러에서 이러한 제한을 구현하는 데 어려움이 있는지 잘 모르겠습니다. 즉, 제한된 정적 상속에 대한 정보 공간이 필요하다는 기본적인 믿음은 기본 디자인이 좋지 않은 (또는 단순한) 디자인 선택으로 인해 불가능하다는 것입니다.


0

정적 클래스와 클래스 멤버는 클래스 인스턴스를 만들지 않고 액세스 할 수있는 데이터와 함수를 만드는 데 사용됩니다. 정적 클래스 멤버를 사용하면 객체 아이덴티티와 무관 한 데이터와 동작을 분리 할 수 ​​있습니다. 데이터와 함수는 객체에 발생하는 내용에 관계없이 변경되지 않습니다. 클래스에 객체 아이덴티티에 의존하는 데이터 나 동작이 없을 때 정적 클래스를 사용할 수 있습니다.

클래스는 static으로 선언 될 수 있으며, 이는 정적 멤버 만 포함 함을 나타냅니다. 새 키워드를 사용하여 정적 클래스의 인스턴스를 작성할 수 없습니다. 정적 클래스는 클래스가 포함 된 프로그램 또는 네임 스페이스가로드 될 때 .NET Framework CLR (공용 언어 런타임)에 의해 자동으로로드됩니다.

정적 클래스를 사용하여 특정 객체와 관련되지 않은 메소드를 포함하십시오. 예를 들어, 인스턴스 데이터에 대해 작동하지 않고 코드의 특정 객체와 관련되지 않은 메소드 세트를 작성하는 것이 일반적 요구 사항입니다. 정적 클래스를 사용하여 해당 메소드를 보유 할 수 있습니다.

정적 클래스의 주요 기능은 다음과 같습니다.

  1. 정적 멤버 만 포함합니다.

  2. 인스턴스화 할 수 없습니다.

  3. 그들은 봉인되어 있습니다.

  4. 인스턴스 생성자를 포함 할 수 없습니다 (C # 프로그래밍 가이드).

따라서 정적 클래스를 만드는 것은 기본적으로 정적 멤버와 개인 생성자 만 포함하는 클래스를 만드는 것과 같습니다. 개인 생성자는 클래스가 인스턴스화되지 않도록합니다.

정적 클래스를 사용하면 컴파일러에서 인스턴스 멤버가 실수로 추가되지 않았는지 확인할 수 있다는 장점이 있습니다. 컴파일러는이 클래스의 인스턴스를 만들 수 없음을 보증합니다.

정적 클래스는 봉인되어 상속 될 수 없습니다. Object를 제외한 모든 클래스에서 상속 할 수 없습니다. 정적 클래스는 인스턴스 생성자를 포함 할 수 없습니다. 그러나 정적 생성자를 가질 수 있습니다. 자세한 내용은 정적 생성자 (C # 프로그래밍 안내서)를 참조하십시오.


-1

정적 멤버와 개인 생성자 만 포함하는 정적 클래스를 만들 때 정적 생성자가 클래스를 인스턴스화하지 못하도록하는 이유는 정적 클래스를 상속 할 수 없기 때문입니다. 정적 클래스를 사용하여 정적 클래스. 정적 클래스를 상속하려고 시도하는 것은 좋지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.