22

答えるために言及するもの:

  1. 問題の項目はむしろ.ArrayT[]

  2. 多次元配列の同様のケースは [ here ]です。

つまり、N-dims から線形への変換は常に可能です。したがって、この質問は特に私の注意を引きました。これIListは、線形インデクサー用に既に実装されているためです。


質問:

私のコードでは、次の宣言があります。

public static Array ToArray<T>(this T source); 

souce私のコードは、(実行時に) 配列を提示する方法を知っています。そして、消費するコードがそのインデクサーに直接アクセスできるようにしようとしています。しかし、「as IList」がないとできない。 戻るにobject[]は、追加の変換/キャストが必要になる場合があります。それは私が防止していることです。 私ができることは次のとおりです。

public static IList ToArray<T>(this T source); 

ToArrayしかし、という名前のメソッドが返すのIListは奇妙に見えると思います。

したがって、私はそれと混同しています:

の宣言にはArray

object IList.this[int index];

我々はできるように

Array a;
a=Array.CreateInstance(typeof(char), 1);
(a as IList)[0]='a';

しかし、私たちはできません

a[0]='a';

として宣言されている場合を除いて

public object this[int index]; 

私が見ることができる唯一の違いは、IList実装されたインターフェイスを介して明示的にインデクサーを使用する必要があることですが、なぜですか? メリットはありますか?それとも、露出の問題がありますか?

4

9 に答える 9

20

Array任意の数の次元で配列を表すことができる必要があるため、インデクサーを持つことはできません。2 次元配列のインデクサーには、1 次元配列とは異なるシグネチャがあります。

インデクサーが提供されArray、2 次元配列を表す で使用された場合、どうなるでしょうか?

言語設計者が選択した解決策は、インデクサーをまったく含めないことでした。

ToArrayメソッドが常に 1 次元配列を返すことがわかっている場合は、次の使用を検討してください。

public static T[] ToArray<T>(this T source); 

それにはインデクサーがあります。

配列内のすべての要素が型ではない場合はT、次を返すことができますobject[]

public static object[] ToArray<T>(this T source); 
于 2013-01-30T18:26:04.233 に答える
5

a as IList(基本的に)キャストです。したがって、最初にキャストするだけです:

char[] a = (char[])Array.CreateInstance(typeof(char), 1);
a[0] = 'a';

編集:理由は、のインターフェイスがArray単にインデクサーを定義していないためです。と を使用SetValue(Object, Int32)Object GetValue(Int32)ます。そこにある不吉なObjectものに注意してください。Arrayタイプ固有ではありません。最小公分母のために構築されています: Object. インデクサーを同じように簡単に定義できたかもしれませんが、実際には、アン/ボックス化の問題がまだ残っています。

于 2013-01-26T02:17:15.993 に答える
4

Arrayがそのインデクサーを直接実装しない理由の 1 つは、すべての特定の配列型 ( などchar[]) が から派生しているためだと思いますArray

これが意味することは、次のようなコードは正当であるということです:

char[] array = new char[10];
array[0] = new object();

このようなコードは、型安全ではないため、正当であってはなりません。以下は正当であり、例外をスローします。

char[] array = new char[10];
array.SetValue(new object(), 0);

しかしSetValue()普段は使わないので大きな問題にはなりません。

于 2013-01-26T03:32:19.390 に答える
3

IList<T>クラス内の のメソッド (Arrayそのインデクサーを含む)の問題は、それらの明示的な実装が実行時にクラスのオブジェクトに追加されることArrayです。

.NET Framework 2.0 以降、Arrayクラスは、、、およびジェネリック インターフェイスを実装System.Collections.Generic.IList<T>System.Collections.Generic.ICollection<T>ますSystem.Collections.Generic.IEnumerable<T>。実装は実行時に配列に提供されるため、ドキュメント ビルド ツールには表示されません。その結果、ジェネリック インターフェイスはArrayクラスの宣言構文に表示されず、配列をジェネリック インターフェイス型にキャストすることによってのみアクセスできるインターフェイス メンバー (明示的なインターフェイス実装) のリファレンス トピックはありません。

クラスがインターフェイスを明示的に実装する場合、インターフェイス メソッドへのアクセスにはキャストが必要です。

インターフェイスを実装するクラスは、そのインターフェイスのメンバーを明示的に実装できます。メンバーが明示的に実装されている場合、クラス インスタンスを介してアクセスすることはできず、インターフェイスのインスタンスを介してのみアクセスできます。

「通常の」(「明示的」ではなく) インターフェイス実装を提供する際の問題は、Arrayクラスがジェネリックではないという事実です。型パラメーターがないと、次のように記述できません。

class Array : IList<T>

は単にT定義されていないためです。環境は、パラメーターの型が判明するまで、インターフェイスの実装をArrayクラスに平手打ちすることはできませんT。これは、実行時にのみ発生する可能性があります。

// The type of [T] is char
Array a = Array.CreateInstance(typeof(char), 1);
// The type of [T] is int
Array b = Array.CreateInstance(typeof(int), 1);
// The type of [T] is string
Array c = Array.CreateInstance(typeof(string), 1);

同時に、ab、およびの静的型はc同じままです - それはSystem.Array. ただし、実行時aには が実装されIList<char>bが実装されIList<int>、およびc-が実装されIList<string>ます。コンパイル時にはどれも知られていないため、コンパイラがインデクサーやその他のIList<T>.

Arrayさらに、実装のすべてのインスタンスではありませんIList<T>-単一次元の配列のみが実行します:

Array x = new int[5];
Console.WriteLine(x.GetType().GetInterface("IList`1") != null); // True
Array y = new int[5,5];
Console.WriteLine(y.GetType().GetInterface("IList`1") != null); // False

上記のすべてにより、コンパイラはIList<T>、明示的なキャストなしで、インデクサーを含むメソッドにアクセスできなくなります。

于 2013-02-01T18:02:15.020 に答える
2

短い答え:

System.Array はND 配列(1 次元だけでなく) の基本クラスであるため、1 次元インデクサー (オブジェクト this[i]{get;set;}) を基本メンバーにすることはできません。

長い答え:

たとえば、2 次元配列を作成して IList インデクサーにアクセスしようとすると、次のようになります。

Array a;
a=Array.CreateInstance(typeof(char), 1,1);
(a as IList)[0]='a';

サポートされていない例外が発生します。

良い質問は次のとおりです。

なぜSystem.Array実装IListし、その実装のほとんどが非 1-D 配列IEnumerableをスローするのに??NotSupportedException

言及するもう1つの興味深いこと。技術的には、古典的な意味で内部的にクラスインデクサーを持っている配列はありません。インデクサーの古典的な意味は、プロパティ "Item" + get(+set) メソッドです。リフレクションを深く掘り下げると、typeof(string[])にはインデクサー プロパティがなく、 GetSetの2 つのメソッドしかないことがわかります。これらのメソッドは、string[] クラスで宣言されています (基本クラスではなく、Array.SetValue とは異なります)。 Array.GetValue) であり、コンパイル時のインデックス作成に使用されます。

于 2013-02-01T00:01:07.443 に答える
2

配列がすべて 1D だったとしても共分散と反分散の問題が残ります。

基本クラスに

public Object this[int index] { get; set; }

インデクサー プロパティ、具体的な型のインデクサー プロパティ

public TValue this[int index] { get; set; }

基本型のものと衝突します (パラメーターはセッターのものであるため、戻り値は同じですが、戻り値は異なります)。

非固有のインデクサーを明示的に実装できるため、基本クラスを基本インターフェイスまたは IList や IList などの汎用インターフェイスにキャストすると、これが解決されます。これは

Add(Object value)

対。

Add(TValue value)

メソッド。

多次元の問題、理論的には、1D インデックスと nD インデックスの間の変換を定義することによって克服できます (例: [n] = [n / length(0), n % length(0)])。バッファ。

于 2013-02-03T15:16:16.307 に答える
1

From msdn:

The Array class is the base class for language implementations that support arrays. However, only the system and compilers can derive explicitly from the Array class. Users should employ the array constructs provided by the language.

If it provide you an indexer it contradicts with the original intention of Array class. You should use the compiler implementation.

Again from msdn:

Important: Starting with the .NET Framework 2.0, the Array class implements the System.Collections.Generic.IList, System.Collections.Generic.ICollection, and System.Collections.Generic.IEnumerable generic interfaces. The implementations are provided to arrays at run time, and therefore are not visible to the documentation build tools. As a result, the generic interfaces do not appear in the declaration syntax for the Array class, and there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.

It is an afterthought, I presume.

于 2013-02-06T18:07:42.463 に答える
1

技術的には、2 種類の配列があります。ベクトル型または行列型。ランタイムは Vector 型を次のように参照しSz_Arrayます。これらは、1d 配列を宣言したときに得られる型です*。理由はわかりません。行列型は多次元配列を表します。残念ながら、どちらも継承元でArrayあり、他の中間型はありません。

Array クラスがそのインデクサーを直接公開しないのはなぜですか?

1 次元配列のインデクサーを持っている場合にのみアクセスできる理由は、T[]1 次元配列のインデクサーが IL オペコードを介してランタイムに実装されているためです。

例えば

static T GetFirst<T>(T[] a)
{
   return a[0]:
}

次の il:: に変換します。

L_0000: ldarg.0 
L_0001: ldc.i4.0 
L_0002: ldelem.any !!T
L_0007: ret 

次のC#として

private static T GetFirst<T>(IList<T> a)
{
    return a[0];
}

この IL に変換されます。

L_0000: ldarg.0 
L_0001: ldc.i4.0 
L_0002: callvirt instance !0 [mscorlib]System.Collections.Generic.IList`1<!!T>::get_Item(int32)
L_0007: ret 

したがって、1 つは opcode を使用してldelem.anyおり、もう1 つはcallvirtメソッドを使用していることがわかります。

ランタイムIList<T>,IEnumerable<T>は、実行時に配列を挿入します。MSIL でのそれらのロジックは、クラスにあります。SZArrayHelper

実装を提供するランタイムは、生成された配列ごとに 2 つのヘルパー メソッドも作成し、インデクサーをサポートしていない言語 (そのような言語が存在する場合) をサポートします。C# はそれらを公開しませんが、呼び出すのに有効なメソッドです。彼らです:

T Get(Int32)
void Set(Int32, T)

これらのメソッドは、次元に応じて行列型の配列に対しても生成され、インデクサーを呼び出すときに C# によって使用されます。

ただし、型付き配列であることを実際に指定しない限り、インデクサーは取得されません。Array にはインデクサー メソッドがありません。コンパイル時に問題の配列がベクトル型であり、配列の要素型であることを知る必要があるため、オペコードは使用できません。

しかし、Array は IList を実装しています! それはインデクサーを持っています。私はそれを呼び出すことができませんか?

はい、そうですが、IList メソッドの実装は明示的な実装であるため、キャスト時または一般的な制約によってバインドされたときにのみ C# で呼び出すことができます。おそらく、ベクトル型以外の配列の場合、そのメソッドのいずれかを呼び出すと、サポートされていない例外がスローされるためです。条件付きでのみサポートされているため、ランタイムの作成者は、これが 1 次元配列であることがわかっている場合にキャストすることをおそらく望んでいますが、今は型に名前を付けることはできません。

IList多次元配列の実装がサポートされていない場合、配列が実装されるのはなぜですか?

これはおそらく 1.0 からあった間違いです。何らかの理由で誰かが多次元配列を にキャストしている可能性があるため、今は修正できませんIList。2.0 では、ランタイム マジックを追加して、実行時にベクター型クラスに実装を追加し、1 次元配列のみがIList<T>and を実装するようにしIEnumerable<T>ました。

そして、次の質問を補間することができます::

キャストせずにインデクサーを表示するにはどうすればよいですか? メソッドのシグネチャを次のように変更します::

public static T[] ToArray<T>(this T source)

しかし、あなたの ToArray は T[] を返さず、それ以外の何かを返すと言うかもしれません。戻り値の型を明示的に指定できる場合は、それを行ってください。参照の種類である場合は、いつでも配列の共分散を悪用して戻り値の型を変更object[]できますが、ArrayTypeMismatchException に翻弄されます。そのキャストが不正であるため、valuetype を取得している場合、これは機能しません。この時点で戻ることができIListますが、要素をボックス化していて、依然として ArrayTypeMismatchException に翻弄されています。Array は、すべての配列タイプの基本クラスです。その理由は、次のようなコンテンツにアクセスするのに役立つヘルパー メソッドがあるからですGetValueSetValueまた、1d 配列だけでなく Nd の要素にもアクセスできるように、インデックスの配列を取るオーバーロードがあることに気付くでしょう。例えば

IList myArray = ToArray(myValues);
// myArray is actually a string array.
myArray[0]='a';
// blows up here as you can't explicitly cast a char to a string.

つまり、明示的な型がわからないということです。そして、Arrayimplementationのすべての継承者はIList、あまり意味がない場合でも、1.0 から存在していたので変更できない実装の詳細です。

  • 技術的には、これは 100% 真実ではありません。1 次元のみの行列型配列を作成できます。typeof(int).MakeArrayType(1)このエルドリッチの忌まわしきものは IL で作成するか、 を使用してタイプを作成できますSystem.Int32[*]System.Int32[]
于 2013-02-05T22:39:30.840 に答える
0

C# Specs "12.1.1 The System.Array type" は、「System.Array 自体は配列型ではないことに注意してください」と述べています。

配列型ではないため。

また、「6.1.6 暗黙的な参照変換」には、「単一次元配列型 S[] から System.Collections.Generic.IList およびその基本インターフェイスへ、S から S への暗黙的な ID または参照変換がある場合」と記載されていることに注意してください。 T"

C# 仕様: http://www.microsoft.com/en-us/download/details.aspx?id=7029

インデクサー アクセスが謎に包まれている理由については、別の SO 投稿: 暗黙的 vs 明示的インターフェイスの実装を確認してください。

それが役に立てば幸い。

于 2013-02-06T11:56:18.940 に答える