#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);
}
}