Search code examples
c#data-structurescollectionstype-systemstyped-arrays

A better way to implement collection of array of different types


I'm looking for a semi-general purpose data structure in C# to store arrays of different integer and float types. In some cases, the integers are bit fields where each bit is equally important and loss of precision isn't tolerable. I'm finding this difficult and messy because of the C# type system and my lack of C# fluency.

The project: Ethercat periodic packets arrive and are converted to a structure (Packet) and accumulated as Packet[] over the course of an experiment. Each field of Packet from Packet[] is converted into an array.

I believe I'm looking for a way to 'wrap' these arrays into a single type so they can be part of a collection. Wrapping them has some other advantages (naming, hardware to SI scale factors, etc) to facilitate decoupling the hardware from the later implementation.

My best 'wrapper' is called 'DataWrapper' (simplified below) but with it I've made uncomfortable compromises in storage, loss of precision, use of object and quantity of code.

Is there a 'better' way in C#? My gold standard is the apparently trivial implementation without obvious compromises in Python using list of lists or numpy.arrays.

Could 'object' be used? How? Is it possible to box the whole array or must each array element boxed individually (inefficient)?

I've seen A list of multiple data types? however, seems like a lot of code and advanced programming techniques for what is essentially a List of 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));  
    }
}

Solution

  • The answer to "Is there a better way in C#?" - is yes.

    Use List<dynamic> as the collection for your arrays.

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

    Of course anything could be passed into it - but that can be tested for.

    List<dynamic> is better in this situation than ArrayList as when attempting to index an array taken from 'list' the IDE flags an error.

    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
    }
    

    A complete illustration follows (but it only conceptually replicates my above wrapper).

    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("");
                }
    
            }
        }
    }
    

    I've since learned that https://stackoverflow.com/a/10380448/4462371 is probably more correct technical explanation.