实现不同类型数组集合的更好方法
A better way to implement collection of array of different types
我正在寻找 C# 中的半通用数据结构来存储不同整数和浮点类型的数组。在某些情况下,整数是位字段,其中每一位都同等重要,并且不能容忍精度损失。由于 C# 类型系统和我缺乏 C# 流畅性,我发现这很困难和混乱。
项目:Ethercat 周期性数据包到达并转换为结构(数据包)并在实验过程中累积为 Packet[]
。来自 Packet[]
的 Packet 的每个字段都被转换为一个数组。
我相信我正在寻找一种方法将这些数组 'wrap' 转化为单一类型,以便它们可以成为集合的一部分。包装它们还有一些其他优势(命名、硬件到 SI 比例因子等),以便于将硬件与以后的实现解耦。
我最好的 'wrapper' 被称为 'DataWrapper'(在下面进行了简化)但是我在存储、精度损失、对象使用和代码数量方面做出了令人不安的妥协。
C# 中有 'better' 方法吗?我的黄金标准是在 Python 使用列表列表或 numpy.arrays 中没有明显妥协的明显微不足道的实现。
可以用'object'吗?如何?是否可以将整个数组装箱或必须将每个数组元素单独装箱(效率低下)?
我看过 A list of multiple data types? 但是,对于本质上是列表列表的内容,似乎有很多代码和高级编程技术。
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);
floats
在 C# 中需要持续精度时会出现问题......期间。这很不幸,因为我们都知道我们有多喜欢精确。但我不认为 C# 是唯一患有这种疾病的语言。也就是说,我认为有一种方法可以实现您想要的,您的包装器是一个好的开始。
我不熟悉您使用的是什么(第 3 方库),所以我将坚持提供问题的解决方案。
如果您知道要检索的类型是什么,我建议您使用 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;
}
您能否简单地将数据序列化为 JSON 或 MessagePack 并将其存储为字符串数组?看起来这样实施起来相对简单且易于使用。
作为通用列表方法的反例,我想提一下问题中链接的列表示例不应被视为高级。它使用简单的 C# 接口。
当您希望调试列表的内容或期望不同类型集合的业务逻辑增长时,使用实现相同接口的不同类型可能是更好的解决方案。现在你只有 GetSI() 但它可能会随着更通用的方法而增长,这些方法对每个数据包收集类型都有特定的实现。最后,您的 IDEs 调试器可能不太支持包含通用对象或原始字节的调试列表。接口得到很好的支持。下面显示了说明该想法的实现。
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 纠正我的愚蠢错误,我写代码绝对是灾难性的,所以如果这不能编译,请耐心等待。我认为总体思路很清楚。
"Is there a better way in 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("");
}
}
}
}
我后来了解到 可能是更正确的技术解释。
我正在寻找 C# 中的半通用数据结构来存储不同整数和浮点类型的数组。在某些情况下,整数是位字段,其中每一位都同等重要,并且不能容忍精度损失。由于 C# 类型系统和我缺乏 C# 流畅性,我发现这很困难和混乱。
项目:Ethercat 周期性数据包到达并转换为结构(数据包)并在实验过程中累积为 Packet[]
。来自 Packet[]
的 Packet 的每个字段都被转换为一个数组。
我相信我正在寻找一种方法将这些数组 'wrap' 转化为单一类型,以便它们可以成为集合的一部分。包装它们还有一些其他优势(命名、硬件到 SI 比例因子等),以便于将硬件与以后的实现解耦。
我最好的 'wrapper' 被称为 'DataWrapper'(在下面进行了简化)但是我在存储、精度损失、对象使用和代码数量方面做出了令人不安的妥协。
C# 中有 'better' 方法吗?我的黄金标准是在 Python 使用列表列表或 numpy.arrays 中没有明显妥协的明显微不足道的实现。
可以用'object'吗?如何?是否可以将整个数组装箱或必须将每个数组元素单独装箱(效率低下)?
我看过 A list of multiple data types? 但是,对于本质上是列表列表的内容,似乎有很多代码和高级编程技术。
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);
floats
在 C# 中需要持续精度时会出现问题......期间。这很不幸,因为我们都知道我们有多喜欢精确。但我不认为 C# 是唯一患有这种疾病的语言。也就是说,我认为有一种方法可以实现您想要的,您的包装器是一个好的开始。
我不熟悉您使用的是什么(第 3 方库),所以我将坚持提供问题的解决方案。
如果您知道要检索的类型是什么,我建议您使用 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;
}
您能否简单地将数据序列化为 JSON 或 MessagePack 并将其存储为字符串数组?看起来这样实施起来相对简单且易于使用。
作为通用列表方法的反例,我想提一下问题中链接的列表示例不应被视为高级。它使用简单的 C# 接口。
当您希望调试列表的内容或期望不同类型集合的业务逻辑增长时,使用实现相同接口的不同类型可能是更好的解决方案。现在你只有 GetSI() 但它可能会随着更通用的方法而增长,这些方法对每个数据包收集类型都有特定的实现。最后,您的 IDEs 调试器可能不太支持包含通用对象或原始字节的调试列表。接口得到很好的支持。下面显示了说明该想法的实现。
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 纠正我的愚蠢错误,我写代码绝对是灾难性的,所以如果这不能编译,请耐心等待。我认为总体思路很清楚。
"Is there a better way in 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("");
}
}
}
}
我后来了解到 可能是更正确的技术解释。