web-dev-qa-db-ja.com

C#の配列はどのようにIList <T>を部分的に実装しますか?

ご存知かもしれませんが、C#の配列は、他のインターフェイスの中でもIList<T>を実装しています。どういうわけか、彼らはIList<T>のCountプロパティを公に実装せずにこれを行います!配列にはLengthプロパティのみがあります。

これは、C#/。NETがインターフェイスの実装に関する独自のルールを破ったという明白な例ですか、それとも何かが足りませんか?

98
MgSam

ハンスの答えに照らした新しい答え

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[]であり、UIList<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
    }
}
79
Jon Skeet

ご存知かもしれませんが、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()メソッドの失敗は、私が考えることができる抽象化の唯一のリークです。

84
Hans Passant

IList<T>.Countが実装されています explicitly

int[] intArray = new int[10];
IList<int> intArrayAsList = (IList<int>)intArray;
Debug.Assert(intArrayAsList.Count == 10);

これは、単純な配列変数を使用するときに、CountLengthの両方を直接使用できないようにするためです。

一般に、明示的なインターフェイスの実装は、型のすべてのコンシューマーがそのように考えることなく、特定の方法で型を使用できるようにする場合に使用されます。

Edit:おっと、思い出せない。 ICollection.Countは明示的に実装されます。ジェネリックIList<T>は、 ハンスの説明 として処理されます。

20
dlev

明示的なインターフェイスの実装 。つまり、void IControl.Paint() { }またはint IList<T>.Count { get { return 0; } }のように宣言します。

10
Tim S.

参照元が利用可能な場合:

//----------------------------------------------------------------------------------------
// ! 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をロードし、対応するジェネリックメソッドを見つけます(単純に一致します) メソッド名による)、タイプに対してインスタンス化し、実行します。

(エンファシス鉱山)

ソース (スクロールアップ)。

1
AnorZaken

IListの明示的なインターフェイス実装と同じです。インターフェイスを実装したからといって、そのメンバーがクラスメンバーとして表示される必要があるわけではありません。 does Countプロパティを実装しますが、X []には公開しません。

1
nitzmahone