Метод расширения, который работает с IEnumerable и IQueryable ?

#c# #generics #extension-methods

#c# #обобщения #методы расширения

Вопрос:

Мне нужен метод расширения, который работает как с моим списком, так и с IQueryable . Приведенные ниже методы расширения выполняют это, но затем, если я добавлю другой идентичный метод расширения, но для другого, совершенно не связанного типа, я получаю неоднозначные ошибки компиляции вызовов. Почему это так? Разве компилятор недостаточно умен, чтобы знать, какой метод расширения работает? Я имею в виду, только один из этих вызовов допустим, почему компилятор не может сообщить? Большое спасибо!

 class ClassA
{
  public bool IsActive{ get; set;}
}

class ClassB
{
  public bool IsActive { get; set;}
}


// then here are my extensions

public static T IsActive<T>(this T enumerableOrQueryable, bool isActive)
  where T : IEnumerable<ClassA>
{
  return (T)enumerableOrQueryable.Where(x => x.IsActive == isActive);
}

public static T IsActive<T>(this T enumerableOrQueryable, bool isActive)
  where T : IEnumerable<ClassB>
{
  return (T)enumerableOrQueryable.Where(x => x.IsActive == isActive);
}
  

Комментарии:

1. Почему бы просто не использовать IEnumerable<ClassA> вместо T ?

2. Обратите внимание, на первый взгляд это Where должно быть Single , SingleOrDefault , First , или FirstOrDefault в зависимости от использования — Where для возврата нескольких совпадающих элементов?

3. Я не могу использовать IEnumerable<ClassA>, потому что я хочу, чтобы они работали как для List<ClassA>, так и для IQueryable<ClassA> . Например, если я передаю IQueryable<ClassA>, то я хочу, чтобы IQueryable<ClassA> был возвращаемым типом.

Ответ №1:

Правила перегрузки не учитывают ограничения на методы, которые он рассматривает — он определяет, какая перегрузка является наилучшей, а затем проверяет соответствие ограничений.

Компилятор в точности следует правилам спецификации C #.

Похожие сообщения в блоге:

РЕДАКТИРОВАТЬ: Обратите внимание, что использование «enumerableOrQueryable» всегда преобразует ваше лямбда-выражение в делегат, а не в дерево выражений. Итак, если вы хотите, чтобы он выполнял логику по-другому для базы данных, вам все равно понадобятся изменения.

РЕДАКТИРОВАТЬ: ваша идея также не сработает, потому что вы все равно не получите тот же тип результата — если вы вызываете Where a List<string> , возвращаемое значение не является a List<string> .

Что вы можете сделать, если вы можете ввести новый интерфейс, который будет реализован как ClassA, так и ClassB:

 public static IQueryable<T> IsActive<T>(this IQueryable<T> source, bool isActive)
    where T : ICanBeActive
{
    // Lambda converted to an expression tree
    return source.Where(x => x.IsActive == isActive);
}

public static IEnumerable<T> IsActive<T>(this IEnumerable<T> source,
    bool isActive) where T : ICanBeActive
{
    // Lambda converted to a delegate
    return source.Where(x => x.IsActive == isActive);
}
  

Комментарии:

1. Спасибо! Это был быстрый ответ. Теперь я чувствую себя глупо. Итак, нет никакого способа выполнить то, что я хочу сделать, если я не хочу убедиться, что все мои методы расширения имеют разные имена?

2. @funGhoul: Ну, вы могли бы попробовать что-то вроде моей версии «злого кода», но я не уверен, поможет ли это вам. Честно говоря, я подозреваю, что вы обнаружите, что в любом случае будет понятнее использовать отдельные имена. И я только что подумал о чем-то другом…

3. @funGhoul: смотрите мою правку для получения дополнительных проблем и возможного решения.

4. Большое спасибо! Я отредактирую свой вопрос с тем, что я должен сделать, если кому-то интересно.

5. Поздно на вечеринку, но версия IQueryable не будет работать (по крайней мере, с EF). T также должен быть классом, иначе выражение содержит приведение, которое сбивает с толку базового поставщика.

Ответ №2:

Компилятор не может разрешить двусмысленность из-за общих ограничений. В вашем случае вы не можете просто сделать что-то подобное?

 public static IEnumerable<ClassA> IsActive(this IEnumerable<ClassA> enumerableOrQueryable, bool isActive)
{
  return enumerableOrQueryable.Where(x => x.IsActive == isActive);
}
  

Комментарии:

1. Понятно, спасибо. Я не хочу указывать IEnumerable<> в подписи, потому что я хочу, чтобы тот же тип, который я передаю, передавался обратно. Этот метод всегда будет возвращать IEnumerable<>

Ответ №3:

Вы можете попробовать что-то вроде этого:

 public interface IActivatable
{
    bool IsActive { get; set; }
}

public class ClassA : IActivatable
{
    public bool IsActive{ get; set;}
}

public class ClassB : IActivatable
{
    public bool IsActive { get; set;}
}

public static class Ext
{
    public static IEnumerable<T> IsActive<T>(this IEnumerable<T> collection, bool isActive) where T : IActivatable
    {
        return collection.Where(x => x.IsActive == isActive);
    }
}