实现不同类型数组集合的更好方法

本文关键字:更好 方法 集合 数组 同类型 实现 | 更新日期: 2023-09-27 18:09:39

我正在寻找c#中的半通用数据结构来存储不同的整数和浮点类型的数组。在某些情况下,整数是位字段,其中每个位都同样重要,并且不能容忍精度损失。由于c#类型系统和我对c#的不熟练,我发现这很困难,也很混乱。

项目:Ethercat周期数据包到达并转换为结构(Packet),并在实验过程中积累为Packet[]。来自Packet[]的每个字段被转换成一个数组。

我相信我正在寻找一种方法来"包装"这些数组成一个单一的类型,这样他们就可以成为一个集合的一部分。包装它们还有其他一些优点(命名、硬件到SI的比例因子等),可以方便地将硬件与后来的实现解耦。

我最好的"包装器"被称为"DataWrapper"(如下所示),但是使用它,我在存储、精度损失、对象使用和代码数量方面做出了不舒服的妥协。

在c#中有更好的方法吗?我的黄金标准是在Python中使用列表的列表或numpy.arrays的显然微不足道的实现,没有明显的妥协。

object可以用吗?如何?是否可以将整个数组装箱,或者必须将每个数组元素单独装箱(效率低下)?

我看到了多个数据类型的列表?然而,似乎大量的代码和高级编程技术本质上是一个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));  
    }
}

实现不同类型数组集合的更好方法

您可以将此处理为byte[]列表并使用BitConverter序列化值类型以将值类型转换为byte[],然后使用反向调用展开它;

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);

您可以简单地将数据序列化为JSON或MessagePack并将其存储为字符串数组吗?似乎这将是相对简单的实现和易于使用。

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;
}

作为通用列表方法的反例,我想指出,问题中链接的列表示例不应被视为高级列表。它使用了一个简单的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纠正我愚蠢的错误,所以如果这不能编译,请忍受我。我想大意已经很清楚了。

"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更好,因为当尝试索引从'list'中获取的数组时,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可能是更正确的技术解释。