Как ловко создать анонимный тип из IEnumerable <T>? - PullRequest
4 голосов
/ 22 марта 2012

Я хотел бы использовать LINQ для решения следующей проблемы, у меня есть следующая коллекция:

List<byte> byteList = new List<byte() { 0x01, 0x00, 0x01, 0x02, 0x01, 0x00, 0x3, 0x4, 0x02 };

Данные в этом примере соответствуют следующей схеме:

byteList [0] = адрес (1, 2, 3, ... n)

byteList [1] = старое состояние, которое в основном является представителем перечисления

byteList [2] = новое состояние, такое же, как указано выше

Я взаимодействую со встроенным устройством, и именно так я могу просматривать изменения во входах.

Чтобы очистить код и сделать так, чтобы программисту, выполняющему техническое обслуживание, было проще следовать моей логике, я бы хотел абстрагироваться от некоторых затягивающихся деталей и извлечь каждый трехбайтовый набор данных в анонимный тип для использоваться в функции для выполнения некоторой дополнительной обработки. Я написал быструю реализацию, но я уверен, что ее можно значительно упростить. Я пытаюсь очистить код, а не мутить воду! Должен быть более простой способ сделать следующее:

List<byte> byteList = new List<byte>()
{
    0x01, 0x09, 0x01, 0x02, 0x08, 0x02, 0x03, 0x07, 0x03
};
var addresses = byteList
    .Where((b, i) => i % 3 == 0)
    .ToList();
var oldValues = byteList
    .Where((b, i) => i % 3 == 1)
    .ToList();
var newValues = byteList
    .Where((b, i) => i % 3 == 2)
    .ToList();

var completeObjects = addresses
    .Select((address, index) => new 
    { 
        Address = address,
        OldValue = oldValues[index],
        NewValue = newValues[index]
    })
    .ToList();
foreach (var anonType in completeObjects)
{
    Console.WriteLine("Address: {0}\nOld Value: {1}\nNew Value: {2}\n",
        anonType.Address, anonType.OldValue, anonType.NewValue);
}

Ответы [ 6 ]

5 голосов
/ 22 марта 2012

Вы можете использовать Enumerable.Range и немного математики:

List<byte> byteList = new List<byte>()
{
    0x01, 0x09, 0x01, 0x02, 0x08, 0x02, 0x03, 0x07, 0x03
};
var completeObjects = Enumerable.Range(0, byteList.Count / 3).Select(index =>
    new
    {
        Address = byteList[index * 3],
        OldValue = byteList[index * 3 + 1],
        NewValue = byteList[index * 3 + 2],
    });

Если количество байтов не кратно 3, дополнительные один или два байта будут игнорироваться.

3 голосов
/ 22 марта 2012

Для упрощения я бы создал тип записи и использовал бы цикл for:

class RecordType
{
    //constructor to set the properties omitted
    public byte Address { get; private set; }
    public byte OldValue { get; private set; }
    public byte NewValue { get; private set; }
}

IEnumerable<RecordType> Transform(List<byte> bytes)
{
    //validation that bytes.Count is divisible by 3 omitted

    for (int index = 0; index < bytes.Count; index += 3)
        yield return new RecordType(bytes[index], bytes[index + 1], bytes[index + 2]);
}

В качестве альтернативы, если вам определенно нужен анонимный тип, вы можете сделать это без linq:

for (int index = 0; index < bytes.Count; index += 3)
{
    var anon = new { Address = bytes[index], OldValue = bytes[index + 1], NewValue = bytes[index + 3] };
    //... do something with anon
}

Linq очень полезен, но неудобен в этой задаче, потому что элементы последовательности имеют различное значение в зависимости от их расположения в последовательности.

0 голосов
/ 22 марта 2012

Вот попытка сделать это с помощью метода расширения ChunkToList, который разбивает IEnumerable<T> на куски IList<T>.

Использование:

        var compObjs = byteList.ChunkToList(3)
                               .Select(arr => new { 
                                       Address  = arr[0],
                                       OldValue = arr[1],
                                       NewValue = arr[2] 
                               });

Реализация:

static class LinqExtensions
{
    public static IEnumerable<IList<T>> ChunkToList<T>(this IEnumerable<T> list, int size)
    {
        Debug.Assert(list.Count() % size == 0);

        int index = 0;
        while (index < list.Count())
        {
            yield return list.Skip(index).Take(size).ToList();
            index += size;
        }
    }
}
0 голосов
/ 22 марта 2012

Как насчет этого?

var addresses = 
    from i in Enumerable.Range(0, byteList.Count / 3)
    let startIndex = i * 3
    select new
    {
        Address = byteList[startIndex],
        OldValue = byteList[startIndex + 1],
        NewValue = byteList[startIndex + 2]
    };

Примечание: я разработал это независимо от ответа Майкла Лю, и, хотя он практически не изменился, я оставлю этот ответ здесь, потому что он выглядит красивее для меня.: -)

0 голосов
/ 22 марта 2012

Если вы должны использовать LINQ (не уверен, что это хороший план), то один из вариантов:

using System;
using System.Collections.Generic;
using System.Linq;

static class LinqExtensions
{
    public static IEnumerable<T> EveryNth<T>(this IEnumerable<T> e, int start, int n)
    {
        int index = 0;
        foreach(T t in e)
        {
            if((index - start) % n == 0)
            {
                yield return t;
            }
            ++index;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<byte> byteList = new List<byte>()
        {
            0x01, 0x09, 0x01, 0x02, 0x08, 0x02, 0x03, 0x07, 0x03
        };

        var completeObjects =
            byteList.EveryNth(0, 3).Zip
            (
                byteList.EveryNth(1, 3).Zip
                (
                    byteList.EveryNth(2, 3),
                    Tuple.Create
                ),
                (f,t) => new { Address = f, OldValue = t.Item1, NewValue = t.Item2 }
            );

        foreach (var anonType in completeObjects)
        {
            Console.WriteLine("Address: {0}\nOld Value: {1}\nNew Value: {2}\n", anonType.Address, anonType.OldValue, anonType.NewValue);
        }
    }
}
0 голосов
/ 22 марта 2012

Я не уверен, что это квалифицируется как умное решение, но я использовал пример, чтобы попытаться выполнить это без создания отдельных списков.

var completeObjects = byteList
    // This is required to access the index, and use integer
    // division (to ignore any reminders) to group them into
    // sets by three bytes in each.
    .Select((value, idx) => new { group = idx / 3, value })
    .GroupBy(x => x.group, x => x.value)

    // This is just to be able to access them using indices.
    .Select(x => x.ToArray())

    // This is a superfluous comment.
    .Select(x => new {
        Address = x[0],
        OldValue = x[1],
        NewValue = x[2]
    })

    .ToList();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...