У меня есть ряд элементов типа int?. Допустим такой:
int?[] row = new int?[] {0,null,1,2,3,0};
Необходимо для всех элементов, которые равны нулю создать некую структуру(Допустим с индексом элемента), остальные отбросить. Логика примерно такая:
MyStruct[] ar = row.Select(
(el, ind) => el == 0 ? new MyStruct {index = ind} : null
)
То есть для всех нулевых элементов создаем структуру, для всех остальных возвращаем null. Далее просто отфильтровываем ненулловые элементы, и превращаем все в массив
.Where(el => el != null).ToArray();
В строке с условием получается ошибка:
Type of conditional expression cannot be determined because there is no implicit conversion between Test.MyStruct and null
Как сделать правильно? Может использовать другой подход? Хочется использовать LINQ а не циклы.
Полный текст программы
using System;
using System.Linq;
public class Test
{
struct MyStruct
{
public int index;
}
public static void Main()
{
int?[] row = new int?[] {0,null,1,2,3,0};
MyStruct[] ar = row.Select(
(el, ind) => el == 0 ? new MyStruct {index = ind} : null
).Where(el => el != null).ToArray();
}
}
или тут
Можно использовать Nullable-структуру:
MyStruct?[] ar = row.Select(
(el, ind) => el == 0 ? (MyStruct?)new MyStruct { index = ind } : null
).Where(el => el!=null).ToArray();
Глядя на Ваш код на IdeOne получаем следующее:
struct MyStruct
{
public int index;
}
private static MyStruct?[] GetRow(int?[] row)
{
return row.Select(
(el, ind) => el == 0 ? (MyStruct?)new MyStruct { index = ind } : null)
.Where(el => el != null).ToArray();
}
static void Main(string[] args)
{
int?[] row = new int?[] { 0, null, 1, 2, 3, 0 };
var ar = GetRow(row);
ar.Select(el => { Console.WriteLine(el?.index); return el; }).ToArray();
Console.WriteLine(ar.Count());
}
http://ideone.com/R0A6lJ
На выходе метода GetRow теперь мы получаем структуру Nullable<MyStruct>. Она имеет два свойства:
bool HasValue - показывает, имеется ли у этого экземпляра значение;MyStructure Value - содержит само значение (если, конечно, оно есть).Поэтому напрямую обратиться к полю index нельзя. Можно написать el.Value.index или воспользоваться новым синтаксисом (начиная, кажется, с C#6 и VS2015) и написать el?.Index.
Проблема, с которой вы столкнулись, заключается в том, что структура не может принимать значение null. Решение "в лоб" - использовать Nullable структуру: MyStruct?.
Вообще говоря, любой value-тип можно неявно привести к nullable-типу. Проблема в том, что компилятор пока не умеет догадываться о том, что null в вашем тернарном операторе - это именно пустой значение nullable-типа, а не объект.
Для справки: у nullable-типов пустое значение только называется null в c#, на самом деле это вполне определенное значение у существующей структуры, которое не имеет ничего общего с null для ссылочных типов.
Поэтому, надо привести одну из веток тернарного оператора к MyStruct? явно.
Получается как-то так: row.Select((el, ind) => el == 0 ? (MyStruct?)new MyStruct {index = ind} : null) или так: row.Select((el, ind) => el == 0 ? new MyStruct {index = ind} : (MyStruct?)null).
Однако, такой трюк изменит тип выражения - а потому надо добавить в конец еще один Select: .Select(x => x.Value).
Все вместе:
row.Select((el, ind) => el == 0 ? new MyStruct {index = ind} : (MyStruct?)null).Where(x => x != null).Select(x => x.Value)
Альтернативные варианты:
Использование SelectMany:
row.Select((el, ind) => el == 0 ? new MyStruct[0] : new [] { new MyStruct {index = ind} })
Для того чтобы пропустить элемент - возвращает пустой массив. Для того, чтобы вернуть элемент - оборачиваем его в массив из одного элемента. Таким образом, метод SelectMany способен заменить Select и Where одновременно.
Использование генератора:
IEnumerable<MyStruct> TransformRow(IEnumerable<int?> numbers) {
var index = 0;
foreach (var number in numbers) {
if (number == 0) {
yield return new MyStruct { index = index };
}
index++;
}
}
// ...
TransformRow(row).ToArray()
Этот способ позволяет писать более сложные алгоритмы, чем позволяет linq to objects. И еще он проще в отладке. Недостатком такого способа является его многословность.
Вот ещё вариант, без MyStruct?, пользующийся тем, что индекс не бывает отрицательным. Семантически, конечно, не очень чисто, зато просто:
int?[] row = new int?[] { 0, null, 1, 2, 3, 0 };
var result = row.Select((v, index) => v == 0 ? index : -1) // индекс хорошего элемента или -1
.Where(index => index >= 0) // отбросили плохие
.Select(idx => new MyStruct() { index = index });
Замените struct на class - и ваш код заработает.
Я сперва сделал класс, но делать целый новый файл с описанием целого нового класса, ради всего одного поля index мне показалось как-то некрасиво.
Классы не обязательно объявлять в отдельных файлах. Вложенные классы - это вполне нормальный механизм языка, точно такой же как вложенные структуры. Никаких практик, разрешающий вложенные структуры, но при этом запрещающие классы, нет.
Заметьте, как упростилась бы задача, если бы вы захотели использовать цикл. Использование Linq, да и вообще любого средства, должно быть оправдано. Тут же вы усложняете себе задачу, лишь бы использовать Linq.
В таких случаях я бы воспользовался анонимным классом
var ar = row.Select((x, i) => x == 0 ? new {value = x, index = i} : null).Where(x => x != null).ToArray();
Дальше этот анонимный класс можно преобразовать в любой необходимый результат через .Select()
Продвижение своими сайтами как стратегия роста и независимости