ご存知かもしれませんが、C#の配列は、他のインターフェイスの中でもIList<T>
を実装しています。どういうわけか、彼らはIList<T>
のCountプロパティを公に実装せずにこれを行います!配列にはLengthプロパティのみがあります。
これは、C#/。NETがインターフェイスの実装に関する独自のルールを破ったという明白な例ですか、それとも何かが足りませんか?
ハンスの答えに照らした新しい答え
Hansの回答のおかげで、実装が思っているよりもやや複雑であることがわかります。コンパイラーとCLRの両方がvery hardを試して、配列型がIList<T>
を実装しているように見えますが、配列の違いによりこれが難しくなります。 Hansの答えとは反対に、特定の配列is n'tの型は、配列型(1次元、ゼロベース)で一般的なコレクションを直接実装します。/System.Array
-これは配列のbaseタイプにすぎません。サポートするインターフェイスを配列型に尋ねると、ジェネリック型が含まれます。
foreach (var type in typeof(int[]).GetInterfaces())
{
Console.WriteLine(type);
}
出力:
System.ICloneable
System.Collections.IList
System.Collections.ICollection
System.Collections.IEnumerable
System.Collections.IStructuralComparable
System.Collections.IStructuralEquatable
System.Collections.Generic.IList`1[System.Int32]
System.Collections.Generic.ICollection`1[System.Int32]
System.Collections.Generic.IEnumerable`1[System.Int32]
languageに関する限り、一次元のゼロベースの配列の場合、配列は実際にIList<T>
も実装します。 C#仕様のセクション12.1.2はそう述べています。そのため、基礎となる実装が何であれ、言語はbehaveで、T[]
の型が他のインターフェイスと同様にIList<T>
を実装する必要があります。この観点から、インターフェイスisは、明示的に実装されるメンバーの一部(Count
など)で実装されます。これは、languageレベルで何が起こっているかについての最良の説明です。
これは、1次元配列(およびゼロから始まる配列であり、言語としてのC#が非ゼロから始まる配列について何も言っていないこと)にのみ当てはまることに注意してください。 T[,]
does n'tIList<T>
を実装します。
CLRの観点からは、ファンキーなことが起こっています。ジェネリックインターフェイスタイプのインターフェイスマッピングを取得することはできません。例えば:
typeof(int[]).GetInterfaceMap(typeof(ICollection<int>))
次の例外を与えます。
Unhandled Exception: System.ArgumentException: Interface maps for generic
interfaces on arrays cannot be retrived.
それで、なぜ奇妙なのですか?まあ、それは本当に配列共分散によるものだと思います。これは型システムIMOのいぼです。 IList<T>
がnot共変(および安全ではない)であっても、配列の共分散によりこれが機能します。
string[] strings = { "a", "b", "c" };
IList<object> objects = strings;
... typeof(string[])
のようにlookになりますが、実際にはそうではありませんが、IList<object>
を実装しています。
CLI仕様(ECMA-335)パーティション1、セクション8.7.1には、次のものがあります。
署名タイプTは、次の少なくとも1つが成り立つ場合にのみ、署名タイプUと互換性があります。
...
Tはゼロベースのランク1配列
V[]
であり、U
はIList<W>
であり、VはWと配列要素互換です。
(実際には、ICollection<W>
やIEnumerable<W>
については言及していませんが、これらは仕様のバグだと思います。)
差異がない場合、CLI仕様は言語仕様に直接沿っています。パーティション1のセクション8.9.1から:
さらに、要素タイプTで作成されたベクトルは、インターフェイス
System.Collections.Generic.IList<U>
を実装します。ここで、U:= Tです(§8.7)
(vectorはゼロベースの1次元配列です。)
implementation detailsに関して、CLRは明らかに割り当ての互換性を保つためにファンキーなマッピングを行っています:string[]
がICollection<object>.Count
の実装を求められたとき、quiteでは通常の方法で処理しません。これは明示的なインターフェース実装としてカウントされますか?インターフェイスマッピングを直接要求しない限り、言語の観点からは常にbehavesのように扱うのが妥当だと思います。
ICollection.Count
はどうですか?
これまで、ジェネリックインターフェイスについて説明しましたが、ICollection
プロパティを持つ非ジェネリックCount
があります。今回は、canインターフェースマッピングを取得します。実際、インターフェースは System.Array
によって直接実装されます。 Array
の- ICollection.Count
プロパティ実装のドキュメントには、明示的なインターフェイス実装で実装されていることが記載されています。
この種の明示的なインターフェイスの実装が「通常の」明示的なインターフェイスの実装と異なる方法を誰かが考えることができるなら、私はそれをさらに調べたいです。
明示的なインターフェイス実装に関する古い回答
上記にも関わらず、配列の知識のためにより複雑ですが、 explicit interface implementation により同じvisible効果で何かをすることができます。
簡単なスタンドアロンの例を次に示します。
public interface IFoo
{
void M1();
void M2();
}
public class Foo : IFoo
{
// Explicit interface implementation
void IFoo.M1() {}
// Implicit interface implementation
public void M2() {}
}
class Test
{
static void Main()
{
Foo foo = new Foo();
foo.M1(); // Compile-time failure
foo.M2(); // Fine
IFoo ifoo = foo;
ifoo.M1(); // Fine
ifoo.M2(); // Fine
}
}
ご存知かもしれませんが、C#の配列は
IList<T>
などのインターフェイスを実装しています
まあ、はい、いや、いや、そうでもない。これは、.NET 4フレームワークのArrayクラスの宣言です。
[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable,
IStructuralComparable, IStructuralEquatable
{
// etc..
}
System.Collections.IList、not System.Collections.Generic.IList <>を実装します。配列はジェネリックではありません。汎用のIEnumerable <>およびICollection <>インターフェイスについても同様です。
ただし、CLRは実行中に具象配列型を作成するため、これらのインターフェイスを実装するものを技術的に作成できます。ただし、そうではありません。たとえば、次のコードを試してください。
using System;
using System.Collections.Generic;
class Program {
static void Main(string[] args) {
var goodmap = typeof(Derived).GetInterfaceMap(typeof(IEnumerable<int>));
var badmap = typeof(int[]).GetInterfaceMap(typeof(IEnumerable<int>)); // Kaboom
}
}
abstract class Base { }
class Derived : Base, IEnumerable<int> {
public IEnumerator<int> GetEnumerator() { return null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
GetInterfaceMap()呼び出しは、 "Interface not found"の具象配列タイプでは失敗します。それでも、IEnumerable <>へのキャストは問題なく機能します。
これはカモに似たタイピングです。すべての値型がObjectから派生するValueTypeから派生するという幻想を作成するのは、同じ種類のタイピングです。コンパイラーとCLRの両方は、値型の場合と同様に、配列型の特別な知識を持っています。コンパイラーは、IList <>にキャストしようとする試みを確認し、「大丈夫、それを行う方法を知っています!」と言います。また、castclass IL命令を発行します。 CLRには問題はありません。基になる配列オブジェクトで機能するIList <>の実装を提供する方法を知っています。それは、これらのインターフェースを実際に実装するラッパーである、隠されているSystem.SZArrayHelperクラスの組み込み知識を持っています。
誰もが主張するように明示的には行いませんが、あなたが尋ねたCountプロパティは次のようになります。
internal int get_Count<T>() {
//! Warning: "this" is an array, not an SZArrayHelper. See comments above
//! or you may introduce a security hole!
T[] _this = JitHelpers.UnsafeCast<T[]>(this);
return _this.Length;
}
はい、あなたは確かにそのコメントを「ルールを破る」と呼ぶことができます:)そして非常によく隠されているので、CLRの共有ソース配布であるSSCLI20でこれを確認できます。 「IList」を検索して、タイプ置換が行われる場所を確認します。アクションでそれを見るのに最適な場所は、clr/src/vm/array.cpp、GetActualImplementationForArrayGenericIListMethod()メソッドです。
CLRでのこの種の置換は、WinRT(別名Metro)のマネージコードの記述を可能にするCLRでの言語プロジェクションで行われることと比較すると、かなり穏やかです。ほぼすべてのコア.NETタイプがそこで置換されます。 IList <>は、たとえば完全に管理されていないタイプのIVector <>にマップされます。それ自体は置換であり、COMはジェネリック型をサポートしていません。
まあ、それはカーテンの後ろで何が起こるかを見ていた。それは、マップの最後に住むドラゴンにとって非常に不快で、奇妙で、なじみのない海です。 Earthをフラットにして、マネージコードで実際に行われていることの異なるイメージをモデル化することは非常に便利です。みんなの好きな答えにそれをマッピングすることは、そのように快適です。これは値の型ではそれほどうまく機能しません(構造体を変更しないでください!)が、これは非常によく隠されています。 GetInterfaceMap()メソッドの失敗は、私が考えることができる抽象化の唯一のリークです。
IList<T>.Count
が実装されています explicitly :
int[] intArray = new int[10];
IList<int> intArrayAsList = (IList<int>)intArray;
Debug.Assert(intArrayAsList.Count == 10);
これは、単純な配列変数を使用するときに、Count
とLength
の両方を直接使用できないようにするためです。
一般に、明示的なインターフェイスの実装は、型のすべてのコンシューマーがそのように考えることなく、特定の方法で型を使用できるようにする場合に使用されます。
Edit:おっと、思い出せない。 ICollection.Count
は明示的に実装されます。ジェネリックIList<T>
は、 ハンスの説明 として処理されます。
明示的なインターフェイスの実装 。つまり、void IControl.Paint() { }
またはint IList<T>.Count { get { return 0; } }
のように宣言します。
//----------------------------------------------------------------------------------------
// ! READ THIS BEFORE YOU WORK ON THIS CLASS.
//
// The methods on this class must be written VERY carefully to avoid introducing security holes.
// That's because they are invoked with special "this"! The "this" object
// for all of these methods are not SZArrayHelper objects. Rather, they are of type U[]
// where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will
// see a lot of expressions that cast "this" "T[]".
//
// This class is needed to allow an SZ array of type T[] to expose IList<T>,
// IList<T.BaseType>, etc., etc. all the way up to IList<Object>. When the following call is
// made:
//
// ((IList<T>) (new U[n])).SomeIListMethod()
//
// the interface stub dispatcher treats this as a special case, loads up SZArrayHelper,
// finds the corresponding generic method (matched simply by method name), instantiates
// it for type <T> and executes it.
//
// The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be
// array that is castable to "T[]" (i.e. for primitivs and valuetypes, it will be exactly
// "T[]" - for orefs, it may be a "U[]" where U derives from T.)
//----------------------------------------------------------------------------------------
sealed class SZArrayHelper {
// It is never legal to instantiate this class.
private SZArrayHelper() {
Contract.Assert(false, "Hey! How'd I get here?");
}
/* ... snip ... */
}
特にこの部分:
インターフェイススタブディスパッチャーはこれを特別なケースとして扱い、SZArrayHelperをロードし、対応するジェネリックメソッドを見つけます(単純に一致します) メソッド名による)、タイプに対してインスタンス化し、実行します。
(エンファシス鉱山)
ソース (スクロールアップ)。
IListの明示的なインターフェイス実装と同じです。インターフェイスを実装したからといって、そのメンバーがクラスメンバーとして表示される必要があるわけではありません。 does Countプロパティを実装しますが、X []には公開しません。