Как реализовать аналог LastOrDefault для коллекции, тип элементов которой неизвестен на этапе компиляции?

«Как реализовать аналог LastOrDefault для коллекции, тип элементов которой неизвестен на этапе компиляции?» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Такая задача возникает при работе с необобщёнными (IEnumerable) или сырыми коллекциями. Реализация требует осторожности из-за потери безопасности типов.

Способ 1: Использование необобщённого IEnumerable (наиболее прямой) Просто перебираем коллекцию, возвращая последний элемент или null.

public static object LastOrDefault(IEnumerable sequence)
{
    if (sequence == null) throw new ArgumentNullException(nameof(sequence));

    object lastElement = null;
    foreach (var item in sequence)
    {
        lastElement = item;
    }
    return lastElement; // Для пустой коллекции вернёт null
}

Способ 2: Обобщённый метод с динамическим определением типа (более гибкий) Позволяет вернуть результат в ожидаемом типе, но использует dynamic с потебой проверки во время выполнения.

public static T LastOrDefault<T>(IEnumerable sequence)
{
    if (sequence == null) throw new ArgumentNullException(nameof(sequence));

    // Пытаемся привести последовательность к IEnumerable<T>
    if (sequence is IEnumerable<T> typedSequence)
    {
        return typedSequence.LastOrDefault(); // Используем встроенный LINQ
    }

    // Если приведение не удалось, перебираем вручную
    T last = default(T);
    foreach (var item in sequence)
    {
        // Это вызовет RuntimeBinderException, если тип item не приводим к T
        last = (T)item; // Небезопасное приведение!
    }
    return last;
}
// Использование: var last = LastOrDefault<string>(someCollection);

Способ 3: Использование рефлексии для вызова обобщённого метода LINQ Мощно, но сложно и медленно. Подходит для библиотечного кода.

public static object LastOrDefault(IEnumerable sequence, Type elementType)
{
    var methodInfo = typeof(Enumerable)
        .GetMethod(nameof(Enumerable.LastOrDefault), BindingFlags.Public | BindingFlags.Static)
        .MakeGenericMethod(elementType); // Создаём обобщённый метод Enumerable.LastOrDefault<T>

    return methodInfo.Invoke(null, new object[] { sequence });
}

Практические рекомендации:

  1. Избегайте dynamic и рефлексии, если есть возможность переработать код для использования обобщённых типов (IEnumerable<T>). Это обеспечит безопасность типов и производительность.
  2. Если тип коллекции неизвестен, но известен тип элемента, используйте Способ 2 с явным приведением и обработкой исключений.
  3. Для полностью универсального подхода (когда неизвестно ничего) Способ 1 — самый безопасный, но он возвращает object, и ответственность за приведение типа ложится на вызывающий код.