Ответ 1
Массивы не реализуют IList<T>
, потому что они могут быть многомерными и ненулевыми.
Однако во время выполнения одномерные массивы с нижней границей нуля автоматически реализуют IList<T>
и некоторые другие общие интерфейсы. Цель этого взлома во время выполнения приведена ниже в двух кавычках.
Здесь http://msdn.microsoft.com/en-us/library/vstudio/ms228502.aspx говорится:
В С# 2.0 и более поздних одномерных массивах, имеющих нижнюю границу нуля автоматически реализует
IList<T>
. Это позволяет создавать общие методы, которые могут использовать один и тот же код для итерации по массивам и другие типы коллекций. Этот метод в первую очередь полезен для чтение данных в коллекциях. ИнтерфейсIList<T>
не может использоваться для добавлять или удалять элементы из массива. Исключение будет выбрано, если вы пытаетесь вызвать методIList<T>
, такой какRemoveAt
в массиве в этот контекст.
В своей книге Джеффри Рихтер говорит:
Команда CLR не захотела
System.Array
реализоватьIEnumerable<T>
,ICollection<T>
иIList<T>
, однако, из-за проблем, связанных с многомерные массивы и ненулевые массивы. Определение этих интерфейсы на System.Array включили бы эти интерфейсы для всех типы массивов. Вместо этого CLR выполняет небольшой трюк: когда создается одномерный тип массива с нулевым нижним пределом, CLR автоматически реализует тип массива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 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
Вторая строка исключает исключение, со следующей командой stacktrace:
DummyClass.Equals(Object obj) в System.Collections.Generic.ObjectEqualityComparer`1.IndexOf(Т [] массива, значение Т, Int32 StartIndex, счетчик Int32) в System.Array.IndexOf(массив T [], значение T) в System.SZArrayHelper.Contains(значение Т)
Теперь ошибка? или Большой вопрос, вот как мы попали в ObjectEqualityComparer из нашего DummyClass, который реализует 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]
Оба используют GenericEqualityComparer, который вызывает метод IEquatable. Фактически Компаратор по умолчанию вызывает следующий метод 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
}
Любопытные части выделены полужирным шрифтом. Очевидно, для DummyClass with Contains мы получили последнюю строку и не пропустили
TypeOf (IEquatable).IsAssignableFrom(с)
проверить!
Почему бы и нет? я предполагаю, что это либо ошибка, либо деталь реализации, которая отличается для структур из-за следующей строки в классе описания SZArrayHelper:
"T" будет отображать интерфейс, используемый для вызова метода. Фактическое время выполнения "this" будет массивом, который можно использовать для "T []" (т.е. Для примитивов и типов значений, он будет точно "T []" - для орфов, это может быть a "U []", где U происходит от T.)
Итак, теперь мы знаем почти все. Единственный вопрос, который остается, заключается в следующем: U не проходит typeof(IEquatable<T>).IsAssignableFrom(c)
check?
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;
}
Теперь я задаюсь вопросом о трех восклицательных знаках и о том, как именно это происходит в таинственном getILIntrinsicImplementation
.