Programing

Array가 제네릭 유형이 아닌 이유는 무엇입니까?

lottogame 2020. 12. 27. 10:18
반응형

Array가 제네릭 유형이 아닌 이유는 무엇입니까?


Array 선언 :

public abstract class Array
    : ICloneable, IList, ICollection, IEnumerable {

왜 그렇지 않은지 궁금합니다.

public partial class Array<T>
    : ICloneable, IList<T>, ICollection<T>, IEnumerable<T> {
  1. 제네릭 유형으로 선언 된 경우 문제는 무엇입니까?

  2. 제네릭 유형이라면 여전히 비 제네릭 유형이 필요 Array<T>합니까? 아니면 파생 될 수 있습니까? 같은

    public partial class Array: Array<object> { 
    

역사

배열이 제네릭 유형이되면 어떤 문제가 발생합니까?

C # 1.0에서는 주로 Java에서 배열 개념을 복사했습니다. 그 당시 Generics는 존재하지 않았지만 제작자는 그들이 똑똑하다고 생각하고 Java 배열이 가진 깨진 공변 배열 의미를 복사했습니다. 즉, 컴파일 타임 오류없이 다음과 같은 작업을 수행 할 수 있습니다 (대신 런타임 오류).

Mammoth[] mammoths = new Mammoth[10];
Animal[] animals = mammoths;            // Covariant conversion
animals[1] = new Giraffe();             // Run-time exception

C # 2.0에서는 제네릭이 도입되었지만 공변 / 반반 변 제네릭 형식은 없습니다. 배열은 일반적인 사항이있는 경우, 당신은 캐스팅하지 못했습니다 Mammoth[]Animal[], (이 파손에도 불구하고) 당신이 전에 할 수있는 일을. 따라서 배열을 일반화 하면 많은 코드 가 손상되었을 것 입니다.

C # 4.0에서만 도입 된 인터페이스에 대한 공변 / 반변 제네릭 형식이있었습니다. 이를 통해 깨진 배열 공분산을 한 번에 고칠 수있었습니다. 그러나 다시 말하지만 이것은 많은 기존 코드를 깨 뜨렸을 것입니다.

Array<Mammoth> mammoths = new Array<Mammoth>(10);
Array<Animal> animals = mammoths;           // Not allowed.
IEnumerable<Animals> animals = mammoths;    // Covariant conversion

배열은 일반 인터페이스를 구현합니다.

왜 배열 일반적인 구현하지 않는 IList<T>, ICollection<T>그리고 IEnumerable<T>인터페이스를?

모든 배열은 런타임 트릭 덕분 T[] 않는 구현 IEnumerable<T>, ICollection<T>IList<T>자동. 1 보내는 사람 Array클래스에 대한 설명 :

단일 차원 배열은 구현 IList<T>, ICollection<T>, IEnumerable<T>, IReadOnlyList<T>IReadOnlyCollection<T>일반적인 인터페이스를. 구현은 런타임에 배열에 제공되며 결과적으로 일반 인터페이스는 Array 클래스의 선언 구문에 나타나지 않습니다.


배열로 구현 된 인터페이스의 모든 멤버를 사용할 수 있습니까?

아니오. 문서는 다음과 같이 계속됩니다.

이러한 인터페이스 중 하나로 배열을 캐스트 할 때 알아야 할 핵심 사항은 요소를 추가, 삽입 또는 제거하는 멤버가 NotSupportedException.

때문에 (예를 들어)의 그 ICollection<T>Add방법을,하지만 당신은 배열에 무엇을 추가 할 수 없습니다. 예외가 발생합니다. 다음은 런타임에 예외가 발생하는 .NET Framework의 초기 디자인 오류의 또 다른 예입니다.

ICollection<Mammoth> collection = new Mammoth[10];  // Cast to interface type
collection.Add(new Mammoth());                      // Run-time exception

그리고 ICollection<T>공변이 아니기 때문에 (분명한 이유로) 이렇게 할 수 없습니다.

ICollection<Mammoth> mammoths = new Array<Mammoth>(10);
ICollection<Animal> animals = mammoths;     // Not allowed

물론 이제 후드 1 아래에서 배열에 의해 구현되는 공변 IReadOnlyCollection<T>인터페이스있지만 사용이 제한되어 있습니다.Count


기본 클래스 Array

배열이 제네릭이라면 여전히 비 제네릭 Array클래스 가 필요 합니까?

초기에 우리는 그랬습니다. 모든 배열은 제네릭이 아닌 구현 IList, ICollection그리고 IEnumerable자신의 기본 클래스를 통해 인터페이스를 Array. 이것은 모든 배열에 특정 메서드와 인터페이스를 제공하는 유일한 합리적인 방법이며 Array기본 클래스 의 기본 사용입니다 . 열거 형에 대해서도 동일한 선택을 볼 수 있습니다. 이들은 값 유형이지만 Enum; MulticastDelegate.

Array제네릭이 지원되므로 제네릭이 아닌 기본 클래스 를 제거 할 수 있습니까?

예, 모든 배열이 공유하는 메서드와 인터페이스는 제네릭 Array<T>클래스가 존재하는 경우 정의 할 수 있습니다 . 그런 다음 예를 들어 형식 안전성의 추가 이점 Copy<T>(T[] source, T[] destination)대신에 작성할 수 Copy(Array source, Array destination)있습니다.

그러나 객체 지향 프로그래밍 관점에서 요소의 유형에 관계없이 모든 배열 Array을 참조하는 데 사용할 수 있는 일반적인 비 제네릭 기본 클래스를 갖는 것이 좋습니다 . (일부 LINQ 메서드에서 여전히 사용되는) 상속 방법과 같습니다 .IEnumerable<T>IEnumerable

Array기본 클래스에서 파생 Array<object>?

아니요, 순환 종속성이 생성 Array<T> : Array : Array<object> : Array : ...됩니다.. 또한 이는 배열에 모든 객체를 저장할 수 있음을 의미합니다 (결국 모든 배열은 궁극적으로 type에서 상 속됨 Array<object>).


미래

Array<T>기존 코드에 너무 많은 영향을주지 않고 새로운 일반 배열 유형 을 추가 할 수 있습니까?

아니요. 구문은 적합하도록 만들 수 있지만 기존 배열 공분산은 사용할 수 없습니다.

배열은 .NET의 특수 유형입니다. Common Intermediate Language로 된 자체 지침도 있습니다. .NET 및 C # 디자이너가이 길을 가기로 결정한 경우 T[]구문 구문 설탕을 만들 수 있으며 Array<T>( T?Syntactic sugar for Nullable<T>) 와 마찬가지로 메모리에 배열을 연속적으로 할당하는 특수 지침과 지원을 사용할 수 있습니다.

그러나으로 캐스트 할 수없는 것과 유사하게 Mammoth[]기본 유형 중 하나로의 배열을 캐스트하는 기능을 잃게 됩니다 . 그러나 배열 공분산은 어쨌든 깨졌고 더 나은 대안이 있습니다.Animal[]List<Mammoth>List<Animal>

배열 공분산의 대안?

모든 배열은 IList<T>. IList<T>인터페이스가 적절한 공변 인터페이스로 만들어진 경우 모든 배열 Array<Mammoth>(또는 해당 문제에 대한 목록)을 IList<Animal>. 그러나 이렇게하려면 IList<T>기본 배열을 변경할 수있는 모든 메서드를 제거하기 위해 인터페이스를 다시 작성해야합니다.

interface IList<out T> : ICollection<T>
{
    T this[int index] { get; }
    int IndexOf(object value);
}

interface ICollection<out T> : IEnumerable<T>
{
    int Count { get; }
    bool Contains(object value);
}

(입력 위치의 매개 변수 유형은 T이것이 공분산을 깨뜨리는 것과 같을 수 없습니다 . 그러나 잘못된 유형의 객체를 전달할 때 반환 되는 에게는 object충분 합니다. 이러한 인터페이스를 구현하는 컬렉션은 고유 한 일반 .)ContainsIndexOffalseIndexOf(T value)Contains(T value)

그런 다음 이렇게 할 수 있습니다.

Array<Mammoth> mammoths = new Array<Mammoth>(10);
IList<Animals> animals = mammoths;    // Covariant conversion

런타임에서 배열 요소의 값을 설정할 때 할당 된 값이 배열 요소의 실제 유형과 호환되는지 여부를 확인할 필요가 없기 때문에 성능이 약간 향상됩니다.


내 찌르기

위에서 설명한 Array<T>실제 공변 IList<T>ICollection<T>인터페이스 와 결합하여 C # 및 .NET으로 구현 될 경우 이러한 유형이 어떻게 작동 하는지 꼼꼼히 살펴 보았습니다. 또한 내 새로운 인터페이스 인터페이스에없는 돌연변이 메서드를 제공하기 위해 불변 IMutableList<T>IMutableCollection<T>인터페이스를 추가했습니다 .IList<T>ICollection<T>

이를 중심으로 간단한 컬렉션 라이브러리를 구축 했으며 BitBucket에서 소스 코드와 컴파일 된 바이너리를 다운로드 하거나 NuGet 패키지를 설치할 수 있습니다.

M42.Collections – 기본 제공 .NET 컬렉션 클래스보다 더 많은 기능, 기능 및 사용 편의성을 갖춘 전문 컬렉션입니다.


1 ) 배열 T[]닷넷의 기본 클래스 구현을 통해 4.5 Array: ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable; 및 자동 실행을 통해 : IList<T>, ICollection<T>, IEnumerable<T>, IReadOnlyList<T>,와 IReadOnlyCollection<T>.


[업데이트, 새로운 통찰력, 지금까지 뭔가 빠진 것 같았습니다.]

이전 답변과 관련하여 :

  • 배열은 다른 유형과 마찬가지로 공변합니다. 'object [] foo = new string [5];'과 같은 것을 구현할 수 있습니다. 공분산으로 인해 그게 이유가 아닙니다.
  • 호환성은 아마도 디자인을 재고하지 않는 이유 일 것입니다. 그러나 나는 이것이 또한 정답이 아니라고 주장합니다.

그러나 내가 생각할 수있는 또 다른 이유는 배열이 메모리의 선형 요소 집합에 대한 '기본 유형'이기 때문입니다. 나는 Array <T>를 사용하는 것에 대해 생각하고 있는데, T가 왜 Object이고 왜이 'Object'가 존재하는지 궁금 할 수도 있습니다. 이 시나리오에서 T []는 Array와 공변하는 Array <T>에 대한 또 다른 구문입니다. 유형이 실제로 다르기 때문에 두 경우가 비슷하다고 생각합니다.

기본 객체와 기본 배열은 모두 OO 언어에 대한 요구 사항이 아닙니다. C ++가 이에 대한 완벽한 예입니다. 이러한 기본 구조에 대한 기본 유형이 없다는주의 사항은 리플렉션을 사용하여 배열이나 객체로 작업 할 수 없다는 것입니다. 사물의 경우 '객체'를 자연스럽게 느끼게하는 Foo 사물을 만드는 데 익숙합니다. 실제로 배열 기본 클래스가 없으면 Foo를 수행하는 것도 똑같이 불가능합니다. 자주 사용되지는 않지만 패러다임에 똑같이 중요합니다.

따라서 Array 기본 유형이없는 C #을 사용하지만 런타임 유형 (특히 리플렉션)이 풍부하면 IMO가 불가능합니다.

자세한 내용은 ...

어레이는 어디에 사용되며 왜 어레이입니까?

배열과 같은 기본적인 것에 대한 기본 유형을 갖는 것은 많은 일에 사용되며 그럴만 한 이유가 있습니다.

  • 단순 배열

예, 우리는 이미 사람들이를 사용하는 T[]것처럼 List<T>. 모두 정확하기 위하여, 인터페이스의 공통 집합을 구현 : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollectionIEnumerable.

이것을 알고 있다면 쉽게 배열을 만들 수 있습니다. 우리는 또한 이것이 사실이라는 것을 알고 있으며 흥미롭지 않으므로 계속 진행하고 있습니다.

  • 컬렉션을 만듭니다.

List를 파헤쳐 보면 결국 Array가 될 것입니다. 정확히 말하면 T [] 배열입니다.

그렇다면 그 이유는 무엇입니까? 포인터 구조 (LinkedList)를 사용할 수는 있지만 동일하지는 않습니다. 목록은 연속적인 메모리 블록이며 연속적인 메모리 블록이 됨으로써 속도를 얻습니다. 여기에는 많은 이유가 있지만 간단히 말하면 연속 메모리를 처리하는 것이 메모리를 처리하는 가장 빠른 방법입니다. CPU에는이를 더 빠르게 만드는 명령도 있습니다.

주의 깊은 독자는이를 위해 배열이 필요하지 않지만 IL이 이해하고 처리 할 수있는 'T'유형 요소의 연속 블록이 필요하다는 사실을 지적 할 수 있습니다. 즉, IL에서 동일한 작업을 수행하는 데 사용할 수있는 다른 유형이 있는지 확인하는 한 여기서 Array 유형을 제거 할 수 있습니다.

값과 클래스 유형이 있습니다. 가능한 최상의 성능을 유지하려면 블록에 저장해야합니다. 마샬링을 위해서는 단순히 요구 사항입니다.

  • 마샬링.

마샬링은 모든 언어가 의사 소통에 동의하는 기본 유형을 사용합니다. 이러한 기본 유형은 byte, int, float, pointer ... 및 array와 같은 것입니다. 특히 C / C ++에서 배열이 사용되는 방식은 다음과 같습니다.

for (Foo *foo = beginArray; foo != endArray; ++foo) 
{
    // use *foo -> which is the element in the array of Foo
}

기본적으로 이것은 배열의 시작 부분에 포인터를 설정하고 배열의 끝에 도달 할 때까지 포인터 (sizeof (Foo) 바이트)를 증가시킵니다. 요소는 * foo에서 검색되며 포인터 'foo'가 가리키는 요소를 가져옵니다.

Note again that there are value types and reference types. You really don't want a MyArray that simply stores everything boxed as an object. Implementing MyArray just got a hell of a lot more tricky.

Some careful readers can point at the fact here that you don't really need an array here, which is true. You need a continuous block of elements with the type Foo - and if it's a value type, it must be stored in the block as the (byte representation of the) value type.

  • Multi-dimensional arrays

So more... What about multi-dimensionality? Apparently the rules aren't so black and white, because suddenly we don't have all the base classes anymore:

int[,] foo2 = new int[2, 3];
foreach (var type in foo2.GetType().GetInterfaces())
{
    Console.WriteLine("{0}", type.ToString());
}

Strong type just went out of the window, and you end up with collection types IList, ICollection and IEnumerable. Hey, how are we supposed to get the size then? When using the Array base class, we could have used this:

Array array = foo2;
Console.WriteLine("Length = {0},{1}", array.GetLength(0), array.GetLength(1));

... but if we look at the alternatives like IList, there's no equivalent. How are we going to solve this? Should introduce a IList<int, int> here? Surely this is wrong, because the basic type is just int. What about IMultiDimentionalList<int>? We can do that and fill it up with the methods that are currently in Array.

  • Arrays have a fixed size

Have you noticed that there are special calls for reallocating arrays? This has everything to do with memory management: arrays are so low-level, that they don't understand what growth or shrinking are. In C you would use 'malloc' and 'realloc' for this, and you really should implement your own 'malloc' and 'realloc' to understand why exactly having fixed sizes is important for all things you directly allocate.

If you look at it, there's only a couple of things that get allocated in a 'fixed' sizes: arrays, all basic value types, pointers and classes. Apparently we handle arrays differently, just like we handle basic types differently.

A side note about type safety

So why need these all these 'access point' interfaces in the first place?

The best practice in all cases is to provide users with a type safe point of access. This can illustrated by comparing code like this:

array.GetType().GetMethod("GetLength").Invoke(array, 0); // don't...

to code like this:

((Array)someArray).GetLength(0); // do!

Type safety enable you to be sloppy when programming. If used correctly, the compiler will find the error if you made one, instead of finding it out run-time. I cannot stress enough how important this is - after all, your code might not be called in a test case at all, while the compiler will always evaluate it!

Putting it all together

So... let's put it all together. We want:

  • A strongly typed block of data
  • That has its data stored continuously
  • IL support to make sure we can use the cool CPU instructions that make it bleeding fast
  • A common interface that exposes all the functionality
  • Type safety
  • Multi-dimensionality
  • We want value types to be stored as value types
  • And the same marshalling structure as any other language out there
  • And a fixed size because that makes memory allocation easier

That's quite a bit of low level requirements for any collection... it requires memory to be organized in a certain way as well as conversion to IL/CPU... I'd say there's a good reason it's considered a basic type.


Compatibility. Array is a historic type that goes back to the time that there were no generics.

Today it would make sense to have Array, then Array<T>, then the specific class ;)


Thus I'd like to know why it is not:

The reason is that generics were not present in the first version of C#.

But I cannot figure out what would be the problem myself.

The problem is that it would break a huge amount of code that uses the Array class. C# doesn't support multiple inheritance, so lines like this

Array ary = Array.Copy(.....);
int[] values = (int[])ary;

would be broken.

If MS were making C# and .NET all over again from scratch, then there probably would be no problem in making Array a generic class, but that is not the reality.


In addition to the other issues people have mentioned, trying to add a generic Array<T> would pose a few other difficulties:

  • Even if today's covariance features had existed from the moment generics were introduced, they wouldn't have been sufficient for arrays. A routine which is designed to sort a Car[] will be able to sort a Buick[], even if it has to copy elements from the array into elements of type Car and then copy them back. The copying of the element from type Car back to a Buick[] isn't really type-safe, but it's useful. One could define a covariant array single-dimensional-array interface in such a way as to make sorting possible [e.g. by including a `Swap(int firstIndex, int secondIndex) method], but it would be difficult to make something that's as flexible as arrays are.

  • While an Array<T> type might work well for a T[], there would be no means within the generic type system to define a family that would include T[], T[,], T[,,], T[,,,], etc. for an arbitrary number of subscripts.

  • There is no means in .net to express the notion that two types should be considered identical, such that a variable of type T1 can be copied to one of type T2, and vice versa, with both variables holding references to the same object. Someone using an Array<T> type would probably want to be able to pass instances to code which expects T[], and accept instances from code which uses T[]. If old-style arrays couldn't be passed to and from code that uses the new style, then the new-style arrays would be more of an obstacle than a feature.

There might be ways of jinxing the type system to allow for a type Array<T> that behaved as it should, but such a type would behave in many ways that were totally different from other generic types, and since there is already a type which implements the desired behavior (i.e. T[]), it's not clear what benefits would accrue from defining another.


As everyone says - original Array is non-generic because there was no generics when it came into existence in v1. Speculation below...

To make "Array" generic (which would make sense now) you can either

  1. keep existing Array and add generic version. This is nice, but most usages of "Array" involve growing it over time and it most likely reason that better implementation of the same concept List<T> was implemented instead. At this point adding generic version of "sequential list of elements that does not grow" does not look very appealing.

  2. remove non-generic Array and replace with generic Array<T> implementation with the same interface. Now you have to make compiled code for older versions to work with new type instead of existing Array type. While it would be possible (also most likely hard) for framework code to support such migration, there is always a lot of code that written by other people.

    As Array is very basic type pretty much every piece of existing code (which includes custom code with reflection and marshalling to with native code and COM) uses it. As result price of even tiny incompatibility between versions (1.x -> 2.x of .Net Framework) would be very high.

So as result Array type is there to stay forever. We now have List<T> as generic equivalent to be used.


Maybe I'm missing something but unless the array instance is casted to or used as an ICollection, IEnumerable, etc.. then you don't gain anything with an array of T.

Arrays are fast and are already type safe and don't incur any boxing/unboxing overhead.

ReferenceURL : https://stackoverflow.com/questions/14324987/why-isnt-array-a-generic-type

반응형