Общий список ‹T› as IEnumerable ‹object›

Я пытаюсь преобразовать список в IEnumerable, чтобы убедиться, что разные списки не являются пустыми или пустыми:

Предположим, что myList - это список ‹T>. Затем в коде вызывающего абонента я хотел:

       Validator.VerifyNotNullOrEmpty(myList as IEnumerable<object>,
                                     @"myList",
                                     @"ClassName.MethodName");

Код проверки будет выглядеть следующим образом:

     public static void VerifyNotNullOrEmpty(IEnumerable<object> theIEnumerable,
                                        string theIEnumerableName,
                                        string theVerifyingPosition)
    {
        string errMsg = theVerifyingPosition + " " + theIEnumerableName;
        if (theIEnumerable == null)
        {
            errMsg +=  @" is null";
            Debug.Assert(false);
            throw new ApplicationException(errMsg);

        }
        else if (theIEnumerable.Count() == 0)
        {
            errMsg +=  @" is empty";
            Debug.Assert(false);
            throw new ApplicationException(errMsg);

        }
    }

Однако это не работает. Он компилируется, но IEnumerable имеет значение null! Почему?


person Avi    schedule 20.05.2010    source источник
comment
Помогают ли хоть какие-нибудь ответы?   -  person Dave D    schedule 24.12.2010
comment
@ Дэйв - Ой. Прости. Отличные ответы. Принял Heinzi, потому что он был больше сосредоточен на том, почему мой код не работал, но ваш ясно объяснил мне, что ДОЛЖНО работать. Спасибо!   -  person Avi    schedule 04.01.2011


Ответы (3)


IEnumerable<object> не является супертипом IEnumerable<T>, поэтому это также не супертип List<T>. См. вопрос 2575363 для краткого обзора того, почему это case (это про Java, но концепции те же). Эта проблема, кстати, решена в C # 4.0, который поддерживает ковариантные обобщения.

Причина, по которой вы не нашли эту ошибку, заключается в том, что вы использовали x as T, где вы должны были использовать обычное приведение ((T)x), см. question 2139798. Получившийся InvalidCastException указал бы вам на вашу ошибку. (Фактически, если бы соотношение типов было правильным (т.е. если бы IEnumerable<object> был супертипом List<T>), вам вообще не понадобилось бы приведение.)

Чтобы решить вашу проблему, сделайте свой метод универсальным, чтобы он принимал IEnumerable<T> вместо IEnumerable<object>, и полностью пропустите приведение.

 public static void VerifyNotNullOrEmpty<T>(IEnumerable<T> theIEnumerable,
                                            string theIEnumerableName,
                                            string theVerifyingPosition) { ... }
person Heinzi    schedule 20.05.2010
comment
В этом случае вы не можете использовать (IEnumerable<object>)myList. - person Kobi; 20.05.2010
comment
@Kobi: Да, именно моя точка зрения: если вы используете (IEnumerable<object>)myList, вы получите InvalidCastException и сразу узнаете, что используете неправильный тип. Если вы используете myList as IEnumerable<object>, вам остается задаться вопросом, почему результат равен нулю (был ли myList нулевым? Или приведение не удалось?). - person Heinzi; 20.05.2010
comment
В порядке. Я не так тебя поняла; Вы, кажется, полагаете, что здесь может работать кастинг. - person Kobi; 20.05.2010
comment
@Kobi: Нет проблем, спасибо за отзыв. Я обновил свой ответ, чтобы прояснить это. - person Heinzi; 20.05.2010

List реализует IEnumerable, поэтому вам не нужно их приводить, вам просто нужно сделать так, чтобы ваш метод принимал общий параметр, например:

 public static void VerifyNotNullOrEmpty<T>(this IEnumerable<T> theIEnumerable,
                                    string theIEnumerableName,
                                    string theVerifyingPosition)
{
    string errMsg = theVerifyingPosition + " " + theIEnumerableName;
    if (theIEnumerable == null)
    {
        errMsg +=  @" is null";
        Debug.Assert(false);
        throw new ApplicationException(errMsg);

    }
    else if (theIEnumerable.Count() == 0)
    {
        errMsg +=  @" is empty";
        Debug.Assert(false);
        throw new ApplicationException(errMsg);

    }
}

Вы должны просто вызвать это с помощью:

var myList = new List<string>
{
    "Test1",
    "Test2"
};

myList.VerifyNotNullOrEmpty("myList", "My position");

Вы также можете немного улучшить реализацию:

 public static void VerifyNotNullOrEmpty<T>(this IEnumerable<T> items,
                                    string name,
                                    string verifyingPosition)
{
    if (items== null)
    {
        Debug.Assert(false);
        throw new NullReferenceException(string.Format("{0} {1} is null.", verifyingPosition, name));
    }
    else if ( !items.Any() )
    {
        Debug.Assert(false);
        // you probably want to use a better (custom?) exception than this - EmptyEnumerableException or similar?
        throw new ApplicationException(string.Format("{0} {1} is empty.", verifyingPosition, name));

    }
}
person Dave D    schedule 20.05.2010
comment
Этот, вероятно, лучше - он демонстрирует, как написать общий метод расширения. Конечно, код странный - добавлять текст в errMsg здесь бесполезно, но не в этом дело. Вдобавок отмечу, что Count() может перебирать всю коллекцию, что может быть нежелательно. Я предпочитаю .Any() проверять, доступны ли какие-либо предметы, хотя это также может иметь побочные эффекты. - person Kobi; 20.05.2010
comment
Я согласен с errMsg, но я действительно не хотел менять его реальную реализацию, когда простое изменение подписи сделало бы то, что ему нужно ... - person Dave D; 20.05.2010
comment
Моя ошибка - сообщение об ошибке переходит в новое исключение, значит, что-то делает. Пропустил этот бит: P. По-прежнему предпочтительны два разных типа исключений. - person Kobi; 20.05.2010

Предположим, вы ориентируетесь как минимум на framework 3.0:

Трансляция в общий IEnumerable<object> с использованием расширения:

var myEnumerable = myList.Cast<object>();

РЕДАКТИРОВАТЬ: в любом случае я бы посоветовал вам изменить свой метод, чтобы получить чистый IEnumerable, например:

public static void VerifyNotNullOrEmpty(IEnumerable theIEnumerable,
                                        string theIEnumerableName,
                                        string theVerifyingPosition)

и внутри метода проверьте, пусто ли оно, используя foreach или theIEnumerable.Cast<object>().Count()

Таким образом, вам не нужно каждый раз использовать IEnumerable<object>

person digEmAll    schedule 20.05.2010
comment
Не работает :-( Теперь вызывающий абонент вызывает: Validator.VerifyNotNullOrEmpty (myList.Cast ‹object› (), ... Callee по-прежнему получает IEnumerable ‹object› theIEnumerable как null - person Avi; 20.05.2010
comment
Вы уверены, что myList не равен нулю? - person digEmAll; 20.05.2010