配列はIList<T>
、多次元でゼロベースでない可能性があるため、実装されません。
ただし、実行時には、下限がゼロの 1 次元配列が自動的に実装されIList<T>
、その他の汎用インターフェイスがいくつか実装されます。このランタイム ハックの目的は、以下の 2 つの引用で詳しく説明されています。
ここでhttp://msdn.microsoft.com/en-us/library/vstudio/ms228502.aspxと書かれています:
C# 2.0 以降では、下限が 0 の 1 次元配列は自動的に を実装しIList<T>
ます。これにより、同じコードを使用して配列やその他のコレクション型を反復処理できるジェネリック メソッドを作成できます。この手法は、主にコレクション内のデータを読み取る場合に役立ちます。IList<T>
インターフェイスを使用して、配列の要素を追加または削除することはできません。このコンテキストで配列IList<T>
などのメソッドを呼び出そうとすると、例外がスローされます。RemoveAt
ジェフリー・リヒターは著書の中で次のように述べています。
ただし、CLR チームは、
、およびSystem.Array
を実装したくありませんでした。これは、多次元配列と非ゼロベースの配列に関連する問題のためです。System.Array でこれらのインターフェイスを定義すると、すべての配列型に対してこれらのインターフェイスが有効になります。代わりに、CLR はちょっとしたトリックを実行します。1 次元のゼロ下限の配列型が作成されると、CLR は自動的に配列型に 、
、および(は配列の要素型) を実装し、さらに 3 つのインターフェイスを実装します。参照型である限り、配列型のすべての基本型。IEnumerable<T>
ICollection<T>
IList<T>
IEnumerable<T>
ICollection<T>
IList<T>
T
さらに掘り下げると、SZArrayHelperは、単一次元のゼロベースの配列に対してこの「ハッキーな」IList 実装を提供するクラスです。
クラスの説明は次のとおりです。
//----------------------------------------------------------------------------------------
// ! 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.)
//----------------------------------------------------------------------------------------
実装が含まれています:
bool Contains<T>(T value) {
//! Warning: "this" is an array, not an SZArrayHelper. See comments above
//! or you may introduce a security hole!
T[] _this = this as T[];
BCLDebug.Assert(_this!= null, "this should be a T[]");
return Array.IndexOf(_this, value) != -1;
}
したがって、次のメソッドを呼び出します
public static int IndexOf<T>(T[] array, T value, int startIndex, int count) {
...
return EqualityComparer<T>.Default.IndexOf(array, value, startIndex, count);
}
ここまでは順調ですね。しかし、ここで最も興味深い/バグのある部分に到達します。
次の例を検討してください(フォローアップの質問に基づいて)
public struct DummyStruct : IEquatable<DummyStruct>
{
public string Name { get; set; }
public bool Equals(DummyStruct other) //<- he is the man
{
return Name == other.Name;
}
public override bool Equals(object obj)
{
throw new InvalidOperationException("Shouldn't be called, since we use Generic Equality Comparer");
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
public class DummyClass : IEquatable<DummyClass>
{
public string Name { get; set; }
public bool Equals(DummyClass other)
{
return Name == other.Name;
}
public override bool Equals(object obj)
{
throw new InvalidOperationException("Shouldn't be called, since we use Generic Equality Comparer");
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
IEquatable<T>.Equals()
非実装の両方に例外スローを植えました。
驚きは次のとおりです。
DummyStruct[] structs = new[] { new DummyStruct { Name = "Fred" } };
DummyClass[] classes = new[] { new DummyClass { Name = "Fred" } };
Array.IndexOf(structs, new DummyStruct { Name = "Fred" });
Array.IndexOf(classes, new DummyClass { Name = "Fred" });
このコードは例外をスローしません。IEquatable Equals の実装に直接到達します。
しかし、次のコードを試すと:
structs.Contains(new DummyStruct {Name = "Fred"});
classes.Contains(new DummyClass { Name = "Fred" }); //<-throws exception, since it calls object.Equals method
2 行目は、次のスタック トレースで例外をスローします。
DummyClass.Equals(Object obj) で System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(T[] 配列、T 値、Int32 startIndex、Int32 カウント) で System.Array.IndexOf(T[] 配列、T 値) でSystem.SZArrayHelper.Contains(T 値)
今バグ?またはここでの大きな質問は、実装する DummyClass から ObjectEqualityComparer にどのように到達したIEquatable<T>
かです。
次のコードのため:
var t = EqualityComparer<DummyStruct>.Default;
Console.WriteLine(t.GetType());
var t2 = EqualityComparer<DummyClass>.Default;
Console.WriteLine(t2.GetType());
プロデュース
System.Collections.Generic.GenericEqualityComparer 1[DummyStruct]
System.Collections.Generic.GenericEqualityComparer
1[DummyClass]
どちらも、IEquatable メソッドを呼び出す GenericEqualityComparer を使用します。実際、CreateComparer メソッドに続いてデフォルトの比較関数が呼び出されます。
private static EqualityComparer<T> CreateComparer()
{
RuntimeType c = (RuntimeType) typeof(T);
if (c == typeof(byte))
{
return (EqualityComparer<T>) new ByteEqualityComparer();
}
if (typeof(IEquatable<T>).IsAssignableFrom(c))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(GenericEqualityComparer<int>), c);
} // RELEVANT PART
if (c.IsGenericType && (c.GetGenericTypeDefinition() == typeof(Nullable<>)))
{
RuntimeType type2 = (RuntimeType) c.GetGenericArguments()[0];
if (typeof(IEquatable<>).MakeGenericType(new Type[] { type2 }).IsAssignableFrom(type2))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(NullableEqualityComparer<int>), type2);
}
}
if (c.IsEnum && (Enum.GetUnderlyingType(c) == typeof(int)))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(EnumEqualityComparer<int>), c);
}
return new ObjectEqualityComparer<T>(); // CURIOUS PART
}
気になる部分は太字で。明らかに、Contains を使用した DummyClass の場合、最後の行に到達し、パスしませんでした
typeof(IEquatable).IsAssignableFrom(c)
小切手!
なぜだめですか?SZArrayHelper 記述クラスの次の行が原因で、構造体とは異なるバグまたは実装の詳細のいずれかだと思います。
「T」は、メソッドの呼び出しに使用されるインターフェースを反映します。実際のランタイム「this」は、「T[]」にキャスト可能な配列になります (つまり、プリミティブと値型の場合、>>正確に「T[]」になります- oref の場合、「U[]」の場合があります)。 U は T から派生します。)
だから私たちは今、ほとんどすべてを知っています。typeof(IEquatable<T>).IsAssignableFrom(c)
残された唯一の疑問は、なぜ U がチェックに合格しないのかということです。
PS: より正確に言うと、SZArrayHelper には SSCLI20 の実装コードが含まれています。現在、実装が変更されているようです。原因は、リフレクターがこのメソッドに対して次のように表示することです。
private bool Contains<T>(T value)
{
return (Array.IndexOf<T>(JitHelpers.UnsafeCast<T[]>(this), value) != -1);
}
JitHelpers.UnsafeCast は、dotnetframework.org の次のコードを示しています
static internal T UnsafeCast<t>(Object o) where T : class
{
// The body of this function will be replaced by the EE with unsafe code that just returns o!!!
// See getILIntrinsicImplementation for how this happens.
return o as T;
}
今、私は 3 つの感嘆符について疑問に思っていgetILIntrinsicImplementation
ます。