IEnumerable.Одиночный и кастинг

#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. Я отредактировал свой ответ, чтобы исключить риск исключения.