제네릭 형식에서 상속하는 것이 좋습니다?


35

List<string>타입 어노테이션 또는 StringList어디 에서 사용하는 것이 더 낫습니까?StringList

class StringList : List<String> { /* no further code!*/ }

나는 Irony 에서 이것들몇 가지 를 만났다 .


제네릭 형식에서 상속하지 않으면 왜 존재합니까?
Mawg

7
@Mawg 인스턴스화에만 사용할 수 있습니다.
Theodoros Chatzigiannakis

3
이것은 코드에서 가장 좋아하는 것 중 하나입니다.
Kik

4
왝. 아뇨, 진심 으로요 사이의 의미 적 차이 무엇 StringListList<string>? 아직 아무것도 없습니다 (일반적인 "귀하")는 프로젝트에만 고유하고 코드를 연구 한 후에야 다른 사람에게 아무것도 의미하지 않는 코드베이스에 또 다른 세부 사항을 추가했습니다. 문자열 목록을 계속 문자열 목록이라고 부르기 위해 문자열 목록의 이름을 숨기는 이유는 무엇입니까? 반면에, 당신이 원한다면 BagOBagels : List<string>나는 그것이 더 합리적이라고 생각합니다. IEnumerable, 외부 소비자는 구현에 신경 쓰지 않고 반복적으로 구현할 수 있습니다.
Craig

1
XAML에는 제네릭을 사용할 수없는 문제가 있으며 목록을 채우기 위해 이와 같은 유형을 만들어야했습니다.
Daniel Little

답변:


27

제네릭 형식에서 상속하려는 또 다른 이유가 있습니다.

메서드 서명에서 제네릭 형식을 중첩하지 않는 것이 좋습니다 .

그런 다음 일부 제네릭에 대해 비즈니스 도메인 이름이 지정된 유형을 만드는 것이 좋습니다. 을 갖는 대신 IEnumerable<IDictionary<string, MyClass>>유형 MyDictionary : IDictionary<string, MyClass>을 만든 다음 멤버가을 IEnumerable<MyDictionary>훨씬 쉽게 읽을 수있게됩니다. 또한 여러 장소에서 MyDictionary를 사용하여 코드의 명확성을 높일 수 있습니다.

이것은 큰 이익처럼 보이지는 않지만 실제 비즈니스 코드에서는 머리카락을 돋보이게하는 제네릭을 보았습니다. 같은 것 List<Tuple<string, Dictionary<int, List<Dictionary<string, List<double>>>>>. 그 일반의 의미는 결코 명백하지 않습니다. 그러나 일련의 명시 적으로 작성된 유형으로 구성된 경우 원래 개발자의 의도가 훨씬 명확 할 것입니다. 또한 어떤 이유로 인해 제네릭 형식을 변경해야하는 경우 해당 제네릭 형식의 모든 인스턴스를 찾아서 바꾸는 것이 파생 클래스에서 변경하는 것과 비교할 때 큰 고통이 될 수 있습니다.

마지막으로 누군가 제네릭에서 파생 된 고유 한 유형을 만들려는 또 다른 이유가 있습니다. 다음 클래스를 고려하십시오.

public class MyClass
{
    public ICollection<MyItems> MyItems { get; private set; }
    // plumbing code here
}

public class MyOtherClass
{
    public ICollection<MyItems> MyItemCache { get; private set; }
    // plumbing code here
}

public class MyConsumerClass
{
    public MyConsumerClass(ICollection<MyItems> myItems)
    {
        // use the collection
    }
}

이제 ICollection<MyItems>MyConsumerClass의 생성자로 전달할 것을 어떻게 알 수 있습니까? 우리는 파생 클래스를 생성하는 경우 class MyItems : ICollection<MyItems>class MyItemCache : ICollection<MyItems>, MyConsumerClass는 실제로 원하는 두 개의 컬렉션 중 어느 대해 매우 구체적으로 할 수 있습니다. 그런 다음 IoC 컨테이너와 매우 잘 작동하여 두 컬렉션을 매우 쉽게 해결할 수 있습니다. 또한 프로그래머가 더 정확하게 MyConsumerClass할 수 있습니다. ICollection과 작업 하는 것이 합리적 이면 일반 생성자를 사용할 수 있습니다. 그러나 파생 된 두 가지 형식 중 하나를 사용하는 것이 비즈니스 적으로 의미가없는 경우 개발자는 생성자 매개 변수를 특정 형식으로 제한 할 수 있습니다.

요약하면, 제네릭 형식에서 형식을 파생시키는 것이 가치가있는 경우가 많습니다. 적절한 경우보다 명확한 코드를 허용하고 가독성 및 유지 관리 성을 도와줍니다.


12
그것은 절대적으로 말도 안되는 Microsoft 권장 사항입니다.
Thomas Eding

7
퍼블릭 API에 대해서는 그렇게 어리석지 않다고 생각합니다. 중첩 된 제네릭은 한 눈에 이해하기 어렵고보다 의미있는 유형 이름은 종종 가독성을 도울 수 있습니다.
Stephen

4
나는 그것이 말도 안된다고 생각하지 않으며, 개인 API에는 확실히 괜찮습니다 ...하지만 공용 API에는 좋은 생각이 아닙니다. 공용 소 비용 API를 작성할 때 가장 기본적인 유형을 수락하고 반환하는 것이 좋습니다.
Paul d' Aoust

35

원칙적으로 일반 형식에서 상속하는 것과 다른 형식에서 상속하는 것에는 차이가 없습니다.

그러나 왜 지구상에서 어떤 클래스 에서 파생되어 더 이상 아무것도 추가하지 않겠습니까?


17
동의한다. 아무것도 기여하지 않고 "StringList"를 읽고 " 정말로 무엇을 합니까?"
Neil

1
또한 "extends"라는 키워드를 사용하여 상속 할 수있는 언어로 클래스를 상속 받으려면 추가 기능으로 클래스를 확장해야한다는 점에 동의합니다.
Elliot Blackburn

14
-1, 이것은 다른 이름을 선택하여 추상화를 만드는 한 가지 방법이라는 것을 이해하지 못했기 때문에 추상화를 만드는 것이이 클래스에 아무 것도 추가하지 않는 매우 좋은 이유가 될 수 있습니다.
Doc Brown

1
내 대답을 생각해보십시오. 누군가가 이것을하기로 결정한 이유 중 일부에 들어갑니다.
NPSF3000

1
"왜 지구상에서 당신은 어떤 클래스에서 파생되고 더 이상 아무것도 추가하지 않겠습니까?" 사용자 컨트롤을 위해했습니다. 일반 사용자 정의 컨트롤을 만들 수는 있지만 Windows 양식 디자이너에서는 사용할 수 없으므로 상속하여 유형을 지정하고 해당 컨트롤을 사용해야합니다.
George T

13

나는 C #을 잘 모르지만 typedef이것이 C ++ 프로그래머가 몇 가지 이유로 일반적으로 사용하는 을 구현하는 유일한 방법이라고 생각합니다 .

  • 코드가 꺾쇠 괄호처럼 보이지 않도록합니다.
  • 나중에 변경이 필요한 경우 다른 기본 컨테이너를 교체 할 수 있으며 그렇게 할 권리가 있음을 분명히 알 수 있습니다.
  • 문맥에보다 적합한 이름을 타입에 부여 할 수 있습니다.

그들은 기본적으로 지루하고 일반적인 이름을 선택하여 마지막 이점을 버렸지 만 다른 두 가지 이유는 여전히 존재합니다.


7
어 .. 그들은 같지 않습니다. C ++에는 리터럴 별명이 있습니다. StringList 이다 a는 List<string>- 그들이 의미로 사용 할 수 있습니다. 전 세계의 모든 사람이를 사용하므로 다른 API를 사용하거나 API를 노출 할 때 중요합니다 List<string>.
Telastyn

2
나는 그들이 똑같은 것이 아니라는 것을 알고 있습니다. 가능한 한 가깝습니다. 약한 버전에는 확실히 단점이 있습니다.
Karl Bielefeldt

24
아니요, using StringList = List<string>;typedef와 가장 가까운 C #입니다. 이와 같은 서브 클래 싱은 인수가있는 생성자를 사용하지 못하게합니다.
Pete Kirkham

4
@PeteKirkham : StringList를 정의하여 using이를 using배치 하는 소스 코드 파일로 제한합니다 . 이 관점에서 상속은에 더 가깝습니다 typedef. 그리고 서브 클래스를 생성한다고해서 필요한 모든 생성자를 추가 할 수는 없습니다 (지루할 수도 있지만).
Doc Brown

2
@ Random832 : 이것을 다르게 표현하겠습니다 : C ++에서 typedef를 사용하여 한 곳 에서 이름을 지정하여 "단일 소스"로 만들 수 있습니다. C #에서는 using이 목적으로 사용할 수 없지만 상속은 가능합니다. 이것은 내가 Pete Kirkham의 공감 된 의견에 동의하지 않는 이유를 보여줍니다. 나는 usingC ++에 대한 실제 대안으로 고려하지 않습니다 typedef.
Doc Brown

9

나는 적어도 하나의 실용적인 이유를 생각할 수 있습니다.

제네릭 형식에서 상속하는 비 제네릭 형식을 선언하면 (아무 것도 추가하지 않아도) 해당 형식을 사용할 수 없거나보다 복잡한 방식으로 사용할 수있는 곳에서 해당 형식을 참조 할 수 있습니다.

제네릭이 아닌 유형 만 사용할 수있는 Visual Studio의 설정 편집기를 통해 설정을 추가하는 경우가 그러한 경우입니다. 거기에 가면 모든 System.Collections수업을 이용할 수 있지만 System.Collections.Generic해당하는 컬렉션은 없습니다 .

다른 경우는 WPF에서 XAML을 통해 유형을 인스턴스화하는 경우입니다. 2009 년 이전 스키마를 사용할 때는 XML 구문에서 형식 인수를 전달할 수 없습니다. 2006 스키마가 새로운 WPF 프로젝트의 기본값 인 것 같습니다.


1
WCF 웹 서비스 인터페이스에서는이 기법을 사용하여 더보기 좋은 컬렉션 유형 이름 (예 : ArrayOfPerson 대신 PersonCollection 등)을 제공 할 수 있습니다.
Rico Suter

7

나는 당신이 어떤 역사 를 조사해야한다고 생각합니다 .

  • C #은 일반적인 유형보다 훨씬 오랫동안 사용되었습니다.
  • 주어진 소프트웨어는 역사상 어느 시점에서 자체 StringList를 가질 것으로 기대합니다.
  • 이것은 ArrayList를 래핑하여 구현되었을 수 있습니다.
  • 그런 다음 코드베이스를 앞으로 이동시키기 위해 리팩토링하는 사람이 있습니다.
  • 그러나 1001 개의 다른 파일을 건드리고 싶지 않았으므로 작업을 완료하십시오.

그렇지 않으면 Theodoros Chatzigiannakis가 가장 좋은 답을 얻었습니다. 예를 들어 일반적인 유형을 이해하지 못하는 도구가 여전히 많이 있습니다. 아니면 그의 대답과 역사가 섞여있을 수도 있습니다.


3
"C #은 일반적인 유형보다 훨씬 오랫동안 사용되었습니다." 실제로 제네릭이없는 C #은 약 4 년 동안 존재했지만 (2002 년 1 월-2005 년 11 월) 제네릭이있는 C #은 이제 9 살입니다.
svick


4

어떤 경우에는 제네릭 형식을 사용할 수 없습니다. 예를 들어 XAML에서 제네릭 형식을 참조 할 수 없습니다. 이 경우 원래 사용하려는 제네릭 형식에서 상속되는 제네릭이 아닌 형식을 만들 수 있습니다.

가능하다면 피할 것입니다.

typedef와 달리 새 유형을 만듭니다. StringList를 메소드의 입력 매개 변수로 사용하는 경우 호출자는을 전달할 수 없습니다 List<string>.

또한 StringList 예제 에서는 그러한 생성자를 정의 new StringList(new[]{"a", "b", "c"})하더라도 말할 수없는 모든 생성자를 명시 적으로 복사해야합니다 List<T>.

편집하다:

허용되는 답변은 도메인 모델 내에서 파생 된 유형을 사용할 때 전문가에게 중점을 둡니다. 나는이 경우에 잠재적으로 유용 할 수 있다고 동의하지만 여전히 개인적으로도 피할 수 있습니다.

그러나 당신은 (내 의견으로는) 호출자의 삶을 더 어렵게 만들거나 성능을 낭비하기 때문에 공개 API에서 절대로 사용해서는 안됩니다 (일반적으로 암시 적 변환은 실제로 좋아하지 않습니다).


3
" XAML에서 제네릭 형식을 참조 할 수 없습니다 "라고 말하지만, 무슨 말을하는지 잘 모르겠습니다.
pswg

1
예를 들어 의미했습니다. 예. 2009 XAML 스키마에서는 가능하지만 2006 스키마에서는 아직 AFAIK가 가능하지만 여전히 VS 기본값입니다.
Lukas Rieger

1
세 번째 단락은 절반에 불과하며 실제로 암시 적 변환기를 사용하여이를 허용 할 수 있습니다.;)
Knerd

1
@Knerd, true, 그러나 당신은 '암시 적으로'새로운 객체를 만듭니다. 더 많은 작업을 수행하지 않으면 데이터 사본도 작성됩니다. 아마도 작은 목록에는 문제가되지 않지만 누군가 수천 개의 요소를 가진 목록을 전달하면 재미가있을 것입니다. =)
Lukas Rieger

@LukasRieger 당신이 맞아 : PI 그냥 지적하고 싶었다 : P
Knerd

2

가치있는 것을 위해, 클래스의 이름을 변경하지 않고 Microsoft의 Roslyn 컴파일러에서 일반 클래스에서 상속하는 방법의 예가 있습니다. (나는 이것에 의해 매우 독창적이어서 이것이 실제로 가능한지 알기 위해 검색에서 끝났습니다.)

CodeAnalysis 프로젝트에서 다음 정의를 찾을 수 있습니다.

/// <summary>
/// Common base class for C# and VB PE module builder.
/// </summary>
internal abstract class PEModuleBuilder<TCompilation, TSourceModuleSymbol, TAssemblySymbol, TTypeSymbol, TNamedTypeSymbol, TMethodSymbol, TSyntaxNode, TEmbeddedTypesManager, TModuleCompilationState> : CommonPEModuleBuilder, ITokenDeferral
    where TCompilation : Compilation
    where TSourceModuleSymbol : class, IModuleSymbol
    where TAssemblySymbol : class, IAssemblySymbol
    where TTypeSymbol : class
    where TNamedTypeSymbol : class, TTypeSymbol, Cci.INamespaceTypeDefinition
    where TMethodSymbol : class, Cci.IMethodDefinition
    where TSyntaxNode : SyntaxNode
    where TEmbeddedTypesManager : CommonEmbeddedTypesManager
    where TModuleCompilationState : ModuleCompilationState<TNamedTypeSymbol, TMethodSymbol>
{
  ...
}

그런 다음 CSharpCodeanalysis 프로젝트에는 다음과 같은 정의가 있습니다.

internal abstract class PEModuleBuilder : PEModuleBuilder<CSharpCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState>
{
  ...
}

이 제네릭이 아닌 PEModuleBuilder 클래스는 CSharpCodeanalysis 프로젝트에서 광범위하게 사용되며 해당 프로젝트의 여러 클래스가 직간접 적으로 상속됩니다.

그런 다음 BasicCodeanalysis 프로젝트에는 다음과 같은 정의가 있습니다.

Partial Friend MustInherit Class PEModuleBuilder
    Inherits PEModuleBuilder(Of VisualBasicCompilation, SourceModuleSymbol, AssemblySymbol, TypeSymbol, NamedTypeSymbol, MethodSymbol, SyntaxNode, NoPia.EmbeddedTypesManager, ModuleCompilationState)

우리는 Roslyn이 C #에 대한 광범위한 지식을 가진 사람들이 작성했다고 가정 할 수 있기 때문에 (어떻게 사용 해야하는지) 이것이 기술의 권장 사항이라고 생각합니다.


예. 또한 선언시 where 절을 직접 구체화하는 데 사용할 수 있습니다.
Sentinel

1

누군가 내 머리 꼭대기에서 그것을 시도하는 이유는 세 가지가 있습니다.

1) 선언을 단순화하려면 :

확실 StringList하고 ListString길이는 같지만 대신 UserCollection실제 Dictionary<Tuple<string,Type>, IUserData<Dictionary,MySerializer>>또는 다른 큰 일반적인 예 를 사용하고 있다고 상상해보십시오 .

2) AOT를 돕기 위해 :

때로는 iOS 용 개발과 같이 Just In Time Compilation이 아닌 Ahead Of Time 컴파일을 사용해야하는 경우가 있습니다. 때때로 AOT 컴파일러는 모든 제네릭 형식을 미리 알아 내기가 어려우므로 힌트를 제공하려는 시도 일 수 있습니다.

3) 기능을 추가하거나 종속성을 제거하려면

어쩌면 그들은 그들이 구현하려는 특정 기능이 StringList(아직 추가, 또는 기록하지, 확장 방법에 숨겨진 수)가 중 하나에 추가 할 수없는 List<string>그것에서 상속하지 않고, 또는 그들이 더럽 싶지 않아 List<string>와 .

또는 그들은 StringList다운 트랙 의 구현을 변경하고 싶을 수도 있으므로 클래스를 마커로 사용했습니다 (보통 인터페이스를 만들거나 사용하십시오 IList).


또한 Irony 에서이 코드를 찾았습니다.이 코드는 상당히 특이한 종류의 응용 프로그램 인 언어 파서입니다. 이것이 사용 된 다른 특정 이유가있을 수 있습니다.

이 예제는 너무 일반적이지 않더라도 그러한 선언을 작성해야하는 합법적 인 이유가있을 수 있음을 보여줍니다. 다른 것과 마찬가지로 몇 가지 옵션을 고려하고 당시에 가장 적합한 것을 선택하십시오.


소스 코드를 살펴보면 옵션 3이 이유 인 것 같습니다.이 기술을 사용하여 기능을 추가하고 컬렉션을 전문화합니다.

public class StringList : List<string> {
    public StringList() { }
    public StringList(params string[] args) {
      AddRange(args);
    }
    public override string ToString() {
      return ToString(" ");
    }
    public string ToString(string separator) {
      return Strings.JoinStrings(separator, this);
    }
    //Used in sorting suffixes and prefixes; longer strings must come first in sort order
    public static int LongerFirst(string x, string y) {
      try {//in case any of them is null
        if (x.Length > y.Length) return -1;
      } catch { }
      if (x == y) return 0;
      return 1; 
    }

1
때로는 선언을 단순화 (또는 무엇이든 단순화)하려는 노력이 복잡성을 줄이는 대신 복잡성을 증가시킬 수 있습니다. 언어를 적당히 알고있는 사람은 누구나 어떤 것이 무엇인지 잘 알고 List<T>있지만, StringList실제로 어떤 언어인지 이해하려면 상황을 조사 해야 합니다 . 실제로 List<string>는 다른 이름으로갑니다. 그러한 단순한 경우에 이것을 수행하는 것의 의미 상 이점은 실제로 보이지 않습니다. 잘 알려진 유형을 다른 유형의 동일한 유형으로 바꾸는 것입니다.
Craig

@Craig 사용 사례에 대한 설명 (더 긴 선언) 및 기타 가능한 이유에 따라 동의합니다.
NPSF3000

-1

나는 그것이 좋은 습관이 아니라고 말하고 싶습니다.

List<string>"일반 문자열 목록"을 전달합니다. 분명히 List<string> 확장 방법이 적용됩니다. 이해하는 데 복잡성이 줄어 듭니다. List 만 필요합니다.

StringList"별도의 클래스의 필요성"을 전달합니다. 아마도 List<string> 확장 메소드가 적용될 수 있습니다 (실제 유형 구현을 확인하십시오). 코드를 이해하려면 더 복잡해야합니다. 목록과 파생 클래스 구현 지식이 필요합니다.


4
어쨌든 확장 방법이 적용됩니다.
Theodoros Chatzigiannakis

2
물론입니다. 그러나 StringList를 검사하고에서 파생 될 때까지 알 수 없습니다 List<string>.
Ruudjah

@TheodorosChatzigiannakis Ruudjah가 "확장 방법은 적용되지 않는다"는 말의 의미에 대해 자세히 설명 할 수 있을지 궁금합니다. 확장 방법은 모든 클래스에서 가능합니다. 물론 .NET 프레임 워크 라이브러리에는 일반 컬렉션 클래스에 적용되는 LINQ라고하는 많은 확장 메서드가 포함되어 있지만 확장 메서드가 다른 클래스에는 적용되지 않는다는 의미는 아닙니다. 아마도 Ruudjah는 언뜻보기에 분명하지 않은 점을 지적하고 있습니까? ;-)
Craig

"확장 방법은 적용되지 않습니다." 이거 어디서 읽어? "확장 방법 적용될 있습니다.
Ruudjah

1
타입 구현은 확장 메소드가 적용되는지 여부를 알려주지 않습니다. 그것이 클래스라는 사실은 확장 메소드 적용 된다는 것을 의미 합니다. 모든 유형의 소비자는 코드와 완전히 독립적으로 확장 메서드를 만들 수 있습니다. 나는 그것이 내가 얻거나 요구하는 것 같아요. LINQ에 대한 이야기입니까? LINQ는 확장 방법을 사용하여 구현 됩니다. 그러나 특정 유형에 대한 LINQ 관련 확장 방법이 없다고해서 해당 유형에 대한 확장 방법이 존재하지 않거나 존재하지 않을 수도 있습니다. 언제든지 누구나 만들 수 있습니다.
Craig
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.