2016-09-30 18 views
3

Я ищу структуру данных с общим общим назначением в C# для хранения массивов разных типов integer и float. В некоторых случаях целые числа являются битовыми полями, где каждый бит одинаково важен, а потеря точности недопустима. Я нахожу это трудным и беспорядочным из-за системы типа C# и моей нехватки C#.Лучший способ реализовать коллекцию массива разных типов

Проект: Периодические пакеты Ethercat прибывают и преобразуются в структуру (Пакет) и накапливаются как Packet[] в ходе эксперимента. Каждое поле пакета от Packet[] преобразуется в массив.

Я считаю, что я ищу способ «обернуть» эти массивы в один тип, чтобы они могли быть частью коллекции. Их упаковка имеет некоторые другие преимущества (присвоение имен, аппаратное обеспечение для коэффициентов шкалы SI и т. Д.), Чтобы облегчить разделение аппаратного обеспечения от последующей реализации.

Моя лучшая «обертка» называется «DataWrapper» (упрощенная ниже), но с ней я столкнулся с неудобными компромиссами при хранении, потерей точности, использованием объекта и количеством кода.

Есть ли «лучший» способ в C#? Мой золотой стандарт - это, по-видимому, тривиальная реализация без очевидных компромиссов в Python с использованием списка списков или numpy.arrays.

Может ли использовать объект? Как? Можно ли поместить весь массив или каждый элемент массива в отдельности (неэффективно)?

Я видел A list of multiple data types? Однако, похоже, много кода и передовых методов программирования для того, что по существу является списком List.

public class DataWrapper 
{ 
    private double[] double_array; // backing with double, but it could if I don't use float 
    private string name; 
    private double scale_factor_to_SI; 

    public DataWrapper(string name, double scale_factor, dynamic dynamic_array) 
    { 

     this.name = name; 
     this.scale_factor_to_SI = scale_factor; 
     this.double_array = new double[dynamic_array.Length]; 

     for (int cnt = 0; cnt < dynamic_array.Length; cnt++) 
     { 
      this.double_array[cnt] = (double)dynamic_array[cnt]; 
     } 
    } 

    public void Get(out int[] i_array) 
    { 
     i_array = this.double_array.Select(item => (int)item).ToArray(); 
    } 

    public void Get(out long[] i_array) 
    { 
     i_array = this.double_array.Select(item => (long)item).ToArray(); 
    } 

    public double[] GetSI() 
    { 
     return this.double_array.Select(item => this.scale_factor_to_SI * (double)item).ToArray(); 
    } 
} 

public struct Packet // this is an example packet - the actual packet is much larger and will change over time. I wish to make the change in 1 place not many. 
{ 
    public long time_uS; 
    public Int16 velocity; 
    public UInt32 status_word; 
}; 

public class example 
{ 
    public Packet[] GetArrayofPacketFromHardware() 
    { 
     return null; 
    } 

    public example() { 
     Packet[] array_of_packet = GetArrayofPacketFromHardware(); 

     var time_uS = array_of_packet.Select(p => p.time_uS).ToArray(); 
     var velocity = array_of_packet.Select(p => p.velocity).ToArray(); 
     var status_bits = array_of_packet.Select(p => p.status_word).ToArray(); 

     List<DataWrapper> collection = new List<DataWrapper> { }; 
     collection.Add(new DataWrapper("time", 1.0e-6, time_uS)); 
     collection.Add(new DataWrapper("velocity", 1/8192, velocity)); 
     collection.Add(new DataWrapper("status", 1, status_bits)); 
    } 
} 
+0

Почему бы не просто передать пакет [], а не проецировать его значения и передать их? –

+0

Я бы посмотрел на «Список ', где 'T' - это класс, в котором есть int, double и float, а также поле типа, в котором указано, какой из них используется. Обычно у меня обычно есть поле «объект» и тип, но вы указали накладные расходы на распаковку. Это проблема, которую вы никогда не решите очень четко на строго типизированном языке, таком как C#. Самое чистое, что вы могли бы потратить на его использование, было бы типом шаблона посетителя для итерации через коллекцию. Тогда вы могли бы, по крайней мере, избежать переключения в поле типа. FWIW. –

+0

Я рассмотрел обертку, которая имеет различную поддержку для каждого типа - имела конструкторы для каждого типа и т. Д. Лучше в некотором роде, но много кода и выход не были очень полезными. Казалось, что должен быть лучший способ. Я был бы в порядке с боксом, если бы был один ящик для каждого массива - не один ящик на элемент. – pathfinder

ответ

0

Ответ на вопрос «Есть ли лучший способ в C#?» - Да.

Использование List<dynamic> в качестве коллекции для ваших массивов.

List<dynamic> array_list = new List<dynamic> { }; 
public void AddArray(dynamic dynamic_array) 
{ 
    this.array_list.Add(dynamic_array); 
} 

Конечно, все может быть передано в него - но это может быть проверено.

List<dynamic> в этой ситуации лучше, чем ArrayList, так как при попытке индексирования массива, взятого из «списка», флаги IDE выдают ошибку.

int ndx = 0; 
foreach (var array_from_list in this.array_list) {     
    var v = array_from_list[ndx]; // error if array_from_list is ArrayList 
} 

Полный рисунок следует (но он только концептуально воспроизводит мою описанную выше обертку).

using System; 
using System.Collections.Generic; 


namespace Application 
{ 
    class MyTest 
    { 
     List<dynamic> array_list = new List<dynamic> { }; 
     int length; 

     public void AddArray(dynamic dynamic_array) 
     { 
      this.array_list.Add(dynamic_array); 
      this.length = dynamic_array.Length; 
     } 
     public dynamic GetVector(int ndx) 
     { 
      return array_list[ndx]; 
     } 
     public void Display() 
     { 
      for (int ndx = 0; ndx < this.length; ndx++) 
      { 
       string ln_txt = ""; 
       foreach (var array_from_list in this.array_list) 
       { 
        string s = array_from_list[ndx].ToString(); 
        ln_txt += $"{s} "; 
       } 

       Console.WriteLine(ln_txt); 
      } 

     } 
    } 

    static class Program 
    { 


     [STAThread] 
     static void Main(string[] args) 
     { 

      MyTest test = new MyTest(); 
      test.AddArray(new long[] { 10, 20, 30, 40 }); 
      test.AddArray(new int[] { 1, 2, 3, 4 }); 
      test.AddArray(new double[] { .1, .2, .3, .4 }); 
      test.AddArray(new string[] { "a", "b", "c", "d" }); 
      test.Display(); 


      for (int vecnum = 0; vecnum < 4; vecnum++) 
      { 
       var vector = test.GetVector(vecnum); 
       Console.Write($"vnum:{vecnum} : "); 

       foreach (var value in vector) 
       { 
        Console.Write($"{value} "); 
       } 
       Console.WriteLine(""); 
      } 

     } 
    } 
} 

Я так понял, что https://stackoverflow.com/a/10380448/4462371, вероятно, более правильно техническое объяснение.

0

Вы могли бы относиться к этому список байт [] и сериализации типы значений с использованием BitConverter для преобразования типа значения в байт [], а затем раскатать его с помощью обратного вызова;

List<byte[]> dataList = new List<byte[]>(); 
float v = 1.0424f; 
byte[] converted = BitConverter.GetBytes(v); 
// put converted into a List<byte[]> 
dataList.Add(converted); 
// Convert it back again 
float z= BitConverter.ToSingle(dataList[0], 0); 
+1

Как вы узнаете, когда нужно преобразовать элемент в 'float' против 'int'? Хотя я думаю, что OP имеет ту же проблему. – Quantic

+0

Сделайте список в список >, так что элемент в списке имеет свой собственный тип, встроенный в него. – PhillipH

0

floats будет проблематичным, когда поддерживаются точность требуется в C# ... период. Это несчастливо, потому что мы все знаем, насколько нам нравится точность. Но я не думаю, что C# - единственный язык, который страдает от этой болезни. Тем не менее, я думаю, что есть способ достичь того, чего вы хотите, и ваша обертка - хорошее начало.

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

Если вы знаете, что тип, который вы извлекаете, я бы рекомендовал использовать byte[]. Таким образом, вы можете эффективно хранить 3 byte[] в одном списке.

var dataList = new List<byte[]>(); 
dataList.Add(ConvertUsTime(p.time_US)); 
dataList.Add(ConvertVelocity(p.velocity)); 
dataList.Add(ConvertStatus(p.status)); 

byte[] ConvertToArray(long usTime) { 
    return BitConverter.GetBytes(usTime); 
} 

byte[] ConvertVelocity(Int16 velocity) { 
    return BitConverter.GetBytes(velocity); 
} 

byte[] ConvertStatus(UInt32 status) { 
    return BitConverter.GetBytes(status); 
} 

... для более универсального метода:

byte[] ConvertValue<T>(T value) where T : struct { 
    // we have to test for type of T and can use the TypeCode for switch statement 
    var typeCode = Type.GetTypeCode(typeof(T)); 

    switch(typeCode) { 
     case TypeCode.Int64: 
      return BitConverter.GetBytes((long)value); 

     case TypeCode.Int16: 
      return BitConverter.GetBytes((Int16)value); 

     case TypeCode.UInt32: 
      return BitConverter.GetBytes((UInt32)value); 
    } 

    return null; 
} 
+1

Я использовал double, так как он получил 53 бит точных целых чисел. Это работает для всех объектов битового флага, о которых я знаю. Только длинный (time_uS) страдает - на данный момент это нормально. Однако ваш подход интересен - esp, так как я на самом деле начинаю с байта [] [], прежде чем он станет Packet, а затем Packet []. – pathfinder

0

Вы можете просто сериализовать данные в формате JSON или MessagePack и сохранить его как массив строк? Похоже, это было бы относительно просто реализовать и прост в использовании.

+0

Зачем переходить во внешнюю библиотеку с большей настройкой/конфигурацией, чем необходимо? «BitConverter» существует именно для этой цели и является частью инфраструктуры .NET. – IAbstract

0

Как пример встречного подхода к обобщенному списку, я хотел бы упомянуть, что пример списка, связанный в вопросе, не следует рассматривать как расширенный. Он использует интерфейс, который является тривиальным C#.

Использование различных типов, реализующих один и тот же интерфейс, может быть лучшим решением, если вы ожидаете отладки содержимого списка или ожидаете, что бизнес-логика для разных типов коллекций будет расти. Сейчас у вас есть только GetSI(), но он может расти с помощью более общих методов, которые имеют конкретные реализации для каждого типа коллекции пакетов. Наконец, отладочные списки отладчиков, содержащие общие объекты или необработанные байты, вероятно, не очень хорошо поддерживаются вашим отладчиком IDE. Интерфейсы хорошо поддерживаются. Ниже приведена реализация, иллюстрирующая идею.

public example() { 

    Packet[] array_of_packet = GetArrayofPacketFromHardware(); 

    var time_uS = array_of_packet.Select(p => p.time_uS).ToArray(); 
    var velocity = array_of_packet.Select(p => p.velocity).ToArray(); 
    var status_bits = array_of_packet.Select(p => p.status_word).ToArray(); 

    List<IPacketCollection> collection = new List<IPacketCollection> { }; 
    collection.Add(new TimePacketCollection(time_uS)); 
    collection.Add(new VelocityPacketCollection(velocity)); 
    collection.Add(new StatusPacketCollection(status_bits)); 

    // Now we have benefits over generic objects or byte arrays. 
    // We can extend our collections with additional logic as your 
    // Application grows or right now already still benefit from the 
    // GetSI you mentioned as a plus in your question. 
    foreach(var velocityPacketCollection in collection.OfType<VelocityPacketCollection>()) { 
     // specific velocity collection things here. 
     // Also your debugger is perfectly happy peeking in the collection. 
    } 

    // or generic looping accessing the GetSI() 
    foreach(var packetCollection in collection) { 
     System.Debug.Println(packetCollection.GetSI()); 
    } 
} 

public interface IPacketCollection { 
    /// <summary> 
    /// Not sure what this method should do but it seems it 
    /// always returns double precision or something? 
    /// </summary> 
    public double[] GetSI; 
} 

public class TimePacketCollection : IPacketCollection { 
    private const double scaleFactor = 1.0e-6; 
    private long[] timePacketArray; 

    public TimePacketCollection(long[] timeArray) { 
     timePacketArray = timeArray; 
    } 

    public double[] GetSI(){ 
     // no IDE available. Not sure if this automatically converts to 
     // double due to multiplication with a double. 
     return timePacketArray.Select(item => scaleFactorToSI * item).ToArray(); 
    } 
} 

public class VelocityPacketCollection : IPacketCollection { 
    private const double scaleFactor = 1/8192; 
    private Int16[] velocityPacketArray; 

    public VelocityPacketCollection (Int16[] velocities) { 
     velocityPacketArray = velocities; 
    } 

    public double[] GetSI(){ 
     // no IDE available. Not sure if this automatically converts to 
     // double due to multiplication with a double. 
     return velocityPacketArray.Select(item => scaleFactorToSI * item).ToArray(); 
    } 
} 

public class StatusPacketCollection : IPacketCollection { 
    private const double scaleFactor = 1.0; 
    private UInt32[] statusPacketArray; 

    public StatusPacketCollection (UInt32[] statuses) { 
     statusPacketArray = statuses; 
    } 

    public double[] GetSI(){ 
     // no IDE available. Not sure if this automatically converts to 
     // double due to multiplication with a double. 
     return statusPacketArray.Select(item => scaleFactorToSI * item).ToArray(); 
    } 
} 

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

+0

bastijn - благодарю вас за подробный ответ. Я нашел решение, которое лучше для меня, и это список pathfinder

+0

Спасибо, что нашли время, чтобы вернуться и поделиться своим ответом. Я считал динамику, но никогда не был большим поклонником, поскольку я работаю в более крупных командах, и отключение проверки времени компиляции (динамики) может легко привести к сбоям во время выполнения, если они не были должным образом проверены/учтены. Я всегда находил это замечательным для небольших решений или быстрого кода, поскольку он работает. Но как только это должно было быть масштабировано для производства и должно быть около нескольких лет, я бы предпочел решение, которое сбой в моей системе сборки, когда оно неправильно используется. Отлично, что вы нашли свой ответ. Вы, должно быть, многому научились! :) – bastijn

+0

Так что я все еще готов к лучшему пути! Самая большая проблема, которую я вижу, заключается в том, что я не могу ограничить тип объектов (во время компиляции), добавленных в массивы базовых типов номеров. Пока это ограничение выполняется, я полагаю, что foreach, length, indexing и select будут работать. – pathfinder