104

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

これは、C#/.NET がインターフェイスの実装に関する独自のルールを破っている露骨な例ですか、それとも何か不足していますか?

4

6 に答える 6

90

ご存知かもしれませんが、C# の配列はIList<T>、他のインターフェイスの中でもとりわけ実装されます。

そうですね、いや、そうではありません。これは、.NET 4 フレームワークの Array クラスの宣言です。

[Serializable, ComVisible(true)]
public abstract class Array : ICloneable, IList, ICollection, IEnumerable, 
                              IStructuralComparable, IStructuralEquatable
{
    // etc..
}

System.Collections.Generic.IList<>ではなく、System.Collections.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() 呼び出しは、「インターフェイスが見つかりません」という具体的な配列型に対して失敗します。それでも、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 はジェネリック型をサポートしていません。

さて、それはカーテンの後ろで何が起こっているかを見た. マップの最後にドラゴンが住んでいると、非常に不快で奇妙でなじみのない海になる可能性があります。地球を平らにし、マネージ コードで実際に何が起こっているかの別のイメージをモデル化すると、非常に便利です。好きな答えをみんなにマッピングするのは、そのままで気持ちいいです。これは値型ではうまく機能しません (構造体を変更しないでください!) が、これは非常にうまく隠されています。GetInterfaceMap() メソッドの失敗は、私が考えることができる抽象化における唯一のリークです。

于 2012-06-22T21:13:25.507 に答える
84

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

Hans からの回答のおかげで、実装が思ったよりもやや複雑であることがわかります。コンパイラと CLR の両方が、配列型が実装されているという印象を与えようと非常に懸命に努力IList<T>しますが、配列の差異により、これはより複雑になります。Hans からの回答に反して、特定の配列の型は配列の基本型ではない ため、配列型 (1 次元、とにかくゼロベース) は汎用コレクションを直接実装します。配列型にサポートするインターフェイスを尋ねると、ジェネリック型が含まれます。System.Array

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]

1 次元のゼロから始まる配列の場合、言語に関する限り、配列も実際に実装されIList<T>ます。C# 仕様のセクション 12.1.2 にそう記載されています。したがって、基礎となる実装が何を行うにしても、言語は実装のタイプが他のインターフェイスと同じように動作する必要があります。この観点から、インターフェイス明示的に実装されている一部のメンバー ( など) で実装されます。これが、言語レベルで何が起こっているかを説明する最良の方法です。T[]IList<T>Count

これは、1 次元配列 (およびゼロベースの配列、言語としての C# が非ゼロベースの配列について何かを述べているわけではありません) にのみ当てはまることに注意してください。実装T[,] しませんIList<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>は共変ではありませが (そして安全ではありません)、配列の共分散により、これが機能します。

string[] strings = { "a", "b", "c" };
IList<object> objects = strings;

...実際には実装されていない場合、実装のように見えます。typeof(string[])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)。

(ベクトルは、基数がゼロの 1 次元配列です。)

実装の詳細に関しては、string[]ここで代入の互換性を維持するために CLR がファンキーなマッピングを行っていることは明らかICollection<object>.Countです。これは明示的なインターフェイスの実装としてカウントされますか? インターフェイスのマッピングを直接要求しない限り、言語の観点からは常にそのように動作するため、そのように扱うことは合理的だと思います。

どうICollection.Countですか?

これまでジェネリック インターフェイスについて説明してきましたがICollection、プロパティを持つ非ジェネリック インターフェイスもありCountます。今回はインターフェイス マッピングを取得できます。実際、インターフェイスは によって直接実装されていSystem.Arrayます。ICollection.Countのプロパティ実装のドキュメントには、Array明示的なインターフェイス実装で実装されていると記載されています。

この種の明示的なインターフェイスの実装が「通常の」明示的なインターフェイスの実装とは異なる方法を誰かが考えられる場合は、さらに詳しく調べていただければ幸いです。

明示的なインターフェイスの実装に関する古い回答

配列の知識があるため、上記はより複雑ですが、明示的なインターフェイスの実装を通じて、同じ目に見える効果で何かを行うことができます。

簡単なスタンドアロンの例を次に示します。

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
    }
}
于 2012-06-22T20:04:31.720 に答える
21

IList<T>.Count明示的に実装されています:

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

Countこれは、単純な配列変数がある場合に、 と の両方をLength直接利用できないようにするためです。

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

編集:おっと、悪い思い出があります。ICollection.Count明示的に実装されています。ジェネリックは以下の Hans descibesIList<T>として扱われます。

于 2012-06-22T20:04:31.923 に答える
10

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

于 2012-06-22T20:04:26.433 に答える
2

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

于 2012-06-22T20:05:32.133 に答える