Надлежащая обнуляемая проверка типов в C #? - PullRequest
4 голосов
/ 04 октября 2008

Хорошо, моя настоящая проблема была в следующем: я реализовывал IList<T>. Когда я добрался до CopyTo(Array array, int index), это было мое решение:

void ICollection.CopyTo(Array array, int index)
{
    // Bounds checking, etc here.
    if (!(array.GetValue(0) is T))
        throw new ArgumentException("Cannot cast to this type of Array.");
    // Handle copying here.
}

Это сработало в моем исходном коде и до сих пор работает. Но у него есть небольшой недостаток, который не был обнаружен, пока я не начал создавать тесты для него, а именно этот:

public void CopyToObjectArray()
{
    ICollection coll = (ICollection)_list;
    string[] testArray = new string[6];

    coll.CopyTo(testArray, 2);
}

Теперь этот тест должен пройти. Он выдает ArgumentException о невозможности сотворения. Зачем? array[0] == null. Ключевое слово is всегда возвращает false при проверке переменной, установленной на null. Теперь, это удобно по разным причинам, включая избегание нулевых разыменований и т. Д. Я наконец-то придумал для проверки типа:

try
{
    T test = (T)array.GetValue(0);
}
catch (InvalidCastException ex)
{
    throw new ArgumentException("Cannot cast to this type of Array.", ex);
}

Это не совсем элегантно, но работает ... Хотя есть лучший способ?

Ответы [ 4 ]

4 голосов
/ 04 октября 2008

Для этого есть метод для типа, попробуйте:

if(!typeof(T).IsAssignableFrom(array.GetElementType()))
3 голосов
/ 04 октября 2008

Единственный способ убедиться в этом - с помощью отражения, но в 90% случаев вы можете избежать затрат, используя array is T[]. Большинство людей собираются передать правильно типизированный массив, так что это будет делать. Но вы всегда должны предоставлять код для проверки отражения, на всякий случай. Вот как выглядит мой общий пример (обратите внимание: я написал это здесь, по памяти, так что это может не скомпилироваться, но это должно дать основную идею):

class MyCollection : ICollection<T> {
   void ICollection<T>.CopyTo(T[] array, int index) {
       // Bounds checking, etc here.
       CopyToImpl(array, index);
   }
   void ICollection.CopyTo(Array array, int index) {
       // Bounds checking, etc here.
       if (array is T[]) { // quick, avoids reflection, but only works if array is typed as exactly T[]
           CopyToImpl((T[])localArray, index);
       } else {
           Type elementType = array.GetType().GetElementType();
           if (!elementType.IsAssignableFrom(typeof(T)) && !typeof(T).IsAssignableFrom(elementType)) {
               throw new Exception();
           }
           CopyToImpl((object[])array, index);
       }
   }
   private void CopyToImpl(object[] array, int index) {
       // array will always have a valid type by this point, and the bounds will be checked
       // Handle the copying here
   }
}

РЕДАКТИРОВАТЬ : Хорошо, забыл кое-что указать. Пара ответов наивно использовала то, что в этом коде читается как element.IsAssignableFrom(typeof(T)). Вы должны также разрешить typeof(T).IsAssignableFrom(elementType), как это делает BCL, в случае, если разработчик знает, что все значения в этом конкретном ICollection на самом деле имеют тип S, полученный из T, и передает массив типа S[]

1 голос
/ 04 октября 2008

List<T> использует это:

try
{
    Array.Copy(this._items, 0, array, index, this.Count);
}
catch (ArrayTypeMismatchException)
{
  //throw exception...
}
0 голосов
/ 13 февраля 2009

Вот небольшой тест попытка / ловля против отражения:

object[] obj = new object[] { };
DateTime start = DateTime.Now;

for (int x = 0; x < 1000; x++)
{
    try
    {
        throw new Exception();
    }
    catch (Exception ex) { }
}
DateTime end = DateTime.Now;
Console.WriteLine("Try/Catch: " + (end - start).TotalSeconds.ToString());

start = DateTime.Now;

for (int x = 0; x < 1000; x++)
{
    bool assignable = typeof(int).IsAssignableFrom(obj.GetType().GetElementType());
}
end = DateTime.Now;
Console.WriteLine("IsAssignableFrom: " + (end - start).TotalSeconds.ToString());

Результирующий вывод в режиме Release:

Try/Catch: 1.7501001
IsAssignableFrom: 0

В режиме отладки:

Try/Catch: 1.8171039
IsAssignableFrom: 0.0010001

Заключение, просто сделай проверку отражения. Оно того стоит.

...