#c# #c#-4.0
#c# #c #-4.0
Вопрос:
У меня есть 2 объекта A и B. B наследуется от A и имеет еще несколько свойств. У меня есть IEnumerable{A}, который содержит только B объектов. Что я хочу сделать, так это:
list.Single(b => b.PropertyThatOnlyExistOnB == "something")
Я бы ожидал, что что-то подобное сработает:
list.Single((B) b => b.PropertyThatOnlyExistOnB == "something")
Но он не компилируется. На данный момент я просто делаю:
B result = null;
foreach (b in list)
{
if((B)b.PropertyThatOnlyExistOnB == "something")
{
result = (B)b;
}
}
Есть ли более короткий путь?
Спасибо
Ответ №1:
Используйте методы Enumerable.OfType<TResult>
расширения для фильтрации / приведения.
list.OfType<B>().Single(b => b.PropertyThatOnlyExistOnB == "something")
Ответ №2:
Хотя мне больше всего нравится ответ @VirtualBlackFox, для полноты картины: вот как заставить вашу идею работать:
list.Single(b => ((B)b).PropertyThatOnlyExistOnB == "something");
Вы были не так уж далеки от правильного пути, за исключением того, что вы перепутали некоторые синтаксические ошибки. b => EXPRESSION
Синтаксис обозначает лямбда-выражение. Вы не можете начать изменять материал перед =>
, если вы не хотите добавлять (или удалять) аргументы:
* `x => LAMBDA_WITH_ONE_PARAMETER`
* `(x) => LAMBDA_WITH_ONE_PARAMETER`
* `() => LAMBDA_WITH_NO_PARAMETERS`
* `(x, y, z) => LAMBDA_WITH_THREE_PARAMETERS`
Комментарии:
1. Вы рискуете создать InvalidCastExceptions . Как вы гарантируете, что в перечислении есть только экземпляры B?
2. Я не такой. ОП говорит, что он уверен,
B
в перечислении есть только экземпляры. Иногда вы просто уверены 😉 Кроме того,Single()
вызовет исключение, еслиB
в перечислении также нет экземпляров. Просто говорю.3. @Matthew Abbot: кроме того, я просто хотел показать OP, как правильно настроить его код. Я помню, что заявлял, что предпочитаю (и, кстати, 1) Ответ VirtualBlackFox!
4. Лол, я не пробивал дыры, это совершенно правильный ответ, но если его определение изменится, это сломается.
Ответ №3:
У меня есть
IEnumerable<A>
, который содержит только B объектов.
Я бы поставил под сомнение это утверждение о вашей переменной. Вы указали, что это an IEnumerable<A>
, но он содержит только экземпляры B
. Какова цель этого? Если вы явно требуете только экземпляры B
при любых обстоятельствах, было бы лучше, чтобы это было an IEnumerable<B>
, поскольку это гарантирует проблемы, которые могут быть обнаружены во время компиляции.
Подумайте о следующем, я бы предположил, что у вас может быть какой-то код, похожий на:
var setOfA = // Get a set of A.
DoSomethingWithA(setOfA);
var instanceOfB = GetInstanceOfB(setOfA);
В этом случае я могу понять, что an IEnumerable<A>
совершенно допустимо, за исключением случаев, когда вы хотите выполнить последнюю операцию, GetInstanceOfB
. Давайте представим, что определение:
B GetInstanceOfB(IEnumerable<A> setOfA)
{
return // The answer to your question.
}
Теперь, первоначальная проблема, которую, я надеюсь, вы видите, заключается в том, что вы вкладываете все свои карты в представление о том, что ваш список ( setOfA
в моем примере) всегда будет содержать только экземпляры B
. Хотя вы можете гарантировать, что с вашей точки зрения разработчика компилятор не может делать таких предположений, он может гарантировать только, что setOfA
(list) является IEnumerable<A>
, и в этом заключается потенциальная проблема.
Просматривая предоставленные ответы (все из которых совершенно верны [@VirtualBlackFox — самый безопасный ответ], учитывая ваше представление):
У меня есть
IEnumerable<A>
, который содержит только B объектов.
Что, если в каком-то будущем изменении, setOfA
, также содержит экземпляр C
(потенциальный будущий подкласс A
) . Учитывая этот ответ:
list.Single(b => ((B)b).PropertyThatOnlyExistOnB == "something");
Что, если setOfA
на самом деле: [C B B]
. Вы можете видеть, что явное приведение (B)b
приведет InvalidCastException
к выбросу. Из-за характера Single
операции перечисление будет продолжаться до тех пор, пока в первом случае что-то не завершится ошибкой с помощью функции predicate ( PropertyThatOnlyExistOnB == "something"
) или не будет выдано исключение. В этом случае может быть выдано исключение, которое является неожиданным и, вероятно, необработанным. Этот ответ похож на:
list.Cast<B>().Single(b => b.PropertyThatOnlyExistOnB == "something");
Учитывая этот ответ:
list.Single<A>(b => (b as B).PropertyThatOnlyExistOnB == "something")
В той же ситуации исключение возникло бы как брошенный экземпляр NullReferenceException
, потому что экземпляр C
не может быть безопасно приведен к типу B
.
Теперь, не поймите меня неправильно, я не выбираю дыры в этих ответах, поскольку я сказал, что они совершенно справедливы, учитывая предмет вашего вопроса. Но в обстоятельствах, когда ваш код изменяется, эти совершенно правильные ответы становятся потенциальными будущими проблемами.
Учитывая этот ответ:
list.OfType<B>.Single(b => b.PropertyThatOnlyExistOnB == "something");
Это позволяет вам безопасно вводить приведение к потенциальному подмножеству A
того, что есть на самом деле B
, и компилятор может гарантировать, что ваш предикат используется только для IEnumerable<B>
.
Но это привело бы меня к обнаружению, что соединение в вашем коде пытается обработать ваш IEnumerable<A>
, но выполнить операцию, в которой вы действительно этого хотите IEnumerable<B>
. В этом случае, не следует ли вам реорганизовать этот код, чтобы, возможно, иметь явный метод:
B GetMatchingInstanceOfB(IEnumerable<B> setOfB)
{
if (setOfB == null) throw new ArgumentNullException("setOfB");
return setOfB.Single(b => b.PropertyThatOnlyExistOnB == "something");
}
Изменение в дизайне метода гарантирует, что он будет явно принимать только допустимый набор B
, и вам не нужно беспокоиться о вашем приведении в этом методе. Метод отвечает только за сопоставление одного элемента B
.
Это, конечно, означает, что вам нужно вывести свое приведение на другой уровень, но это все равно гораздо более явно:
var b = GetMatchingInstanceOfB(setOfA.OfType<B>());
Я также предполагаю, что у вас есть достаточная обработка ошибок в обстоятельствах, когда предикат завершится ошибкой, когда все экземпляры B
, например, удовлетворяют более 1 элемента PropertyThatOnlyExistOnB == "something"
.
Возможно, это была бессмысленная напыщенная речь о проверке вашего кода, но я думаю, что стоит рассмотреть неожиданные ситуации, которые могут возникнуть, и как потенциальная настройка ваших переменных может избавить вас от потенциальной головной боли в будущем.
Ответ №4:
Это должно работать нормально:
list.Single<A>(b => (b as B).PropertyThatOnlyExistOnB == "something")
Если вы не хотите рисковать возникновением исключений, вы можете сделать это:
list.Single<A>(b => ((b is B)amp;amp;((b as B).PropertyThatOnlyExistOnB == "something")))
Комментарии:
1. Не будете ли вы рисковать возможным NullReferenceException, когда элемент не является экземпляром B ?
2. Это либо исключение NullReferenceException, либо исключение InvallidCastException (когда вы ((B)b).Свойство . Ваш выбор 😉
3. rep для четкого отношения 🙂 Я согласен, что нет причин использовать IEnumerable<A>, когда вы уверены, что он содержит только объекты типа B. Я отредактировал свой ответ, чтобы исключить риск исключения.