Надлежащая обнуляемая проверка типов в C #?

StackOverflow https://stackoverflow.com/questions/169562

  •  05-07-2019
  •  | 
  •  

Вопрос

Хорошо, моя настоящая проблема заключалась в следующем: я реализовывал 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);
}

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

Это было полезно?

Решение

Единственный способ убедиться - это рефлексия, но в 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[]

Другие советы

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

if(!typeof(T).IsAssignableFrom(array.GetElementType()))

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

try
{
    Array.Copy(this._items, 0, array, index, this.Count);
}
catch (ArrayTypeMismatchException)
{
  //throw exception...
}

Вот небольшой тест «попробуй / поймай против отражения»:

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());

Результирующий вывод в режиме выпуска:

Try/Catch: 1.7501001
IsAssignableFrom: 0

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

Try/Catch: 1.8171039
IsAssignableFrom: 0.0010001

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top