【问题标题】:Generic Converters for struct to class and vice versa结构到类的通用转换器,反之亦然
【发布时间】:2020-04-08 15:01:28
【问题描述】:

我想要两个这样的转换器:

public class PacMan<T2> where T2 : new()
{
    public static List<T1> ArrayToList<T1>(T2[] array)
    {
        var list = new List<T1>(array.Length);
        for (int i = 0; i < array.Length; i++) list.Add(array[i]);
        return list;
    }

    public static T2[] ListToArray<T1>(List<T1> list)
    {
        var array = new T2[list.Count];
        for (int i = 0; i < list.Count; i++) array[i] = list[i];
        return array;
    }
}

其中T1 是一个类,T2 是一个结构。类和结构成员都具有相同的名称和类型。有了以上内容,我在第一种方法的list.Add(array[i]) 和第二种方法array[i] = list[i] 中得到了红色波浪形,所以这些方法不起作用。最简单的方法是什么?

编辑

课程如下:

public class PerSec : INotifyPropertyChanged
{
    string yq;
    float eps, nav, cash, debt;
    public string YQ { get => yq; set { yq = value; OnPropertyChanged(); } }
    public float EPS { get => eps; set { eps = value; OnPropertyChanged(); } }
    public float NAV { get => nav; set { nav = value; OnPropertyChanged(); } }
    public float Cash { get => cash; set { cash = value; OnPropertyChanged(); } }
    public float Debt { get => debt; set { debt = value; OnPropertyChanged(); } }

    #region Notify Property Changed Members
    public event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    #endregion
}

这是结构:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PerSecStruct
{
    //23 bytes
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)]
    public string YQ;
    public float EPS;
    public float NAV;
    public float Cash;
    public float Debt;
}

编辑

在第二种方法中,我现在有这些:

public static T2[] ListToarray<T1>(List<T1> list)
{
    var structFields = typeof(PerSecStruct).GetFields(BindingFlags.Instance | BindingFlags.Public);
    var classFields = typeof(PerSec).GetProperties(BindingFlags.Instance | BindingFlags.Public);
    classFields = classFields.Where(x => structFields.Select(y => y.Name).Contains(x.Name)).ToArray();
    var fieldsDictionary = structFields.Zip(classFields, (k, v) => new { StructField = k, ClassField = v }).ToDictionary(x => x.StructField, x => x.ClassField);

    var array = new T2[list.Count];
    for (int i = 0; i < list.Count; i++)
    {
        var psStruct = array[i];
        var psClass = list[i];

        foreach (var entry in fieldsDictionary)
        {
            var value = entry.Value.GetValue(psClass);
            entry.Key.SetValue(psStruct, value);
        }
    }
    return array;
}

这条entry.Key.SetValue(psStruct, value); 行不工作,所以数组的元素有它们的默认值(null/0)。

编辑

如果我使用__makeref(array[i]),如 petelids 所述的here,它会起作用。有了它,我可以这样做:

public static T2[] ListToarray<T1>(List<T1> list)
{
    var fields = typeof(T2).GetFields();
    var properties = typeof(T1).GetProperties();
    var array = new T2[list.Count];

    for (int i = 0; i < list.Count; i++)
    {
        foreach (var field in fields)
        {
            var value = properties.First(x => x.Name == field.Name).GetValue(list[i]);
            field.SetValueDirect(__makeref(array[i]), value);
        }
    }
    return array;
}

我不需要那些绑定标志!并且要转换回列表,我必须用其他方法做到这一点:

public static List<T1> ArrayToList<T1>(T2[] array) where T1 : new()
{
    var fields = typeof(T2).GetFields();
    var properties = typeof(T1).GetProperties();

    var list = new List<T1>(array.Length);
    for (int i = 0; i < array.Length; i++)
    {
        var obj = new T1();
        foreach (var property in properties)
        {
            var value = fields.First(x => x.Name == property.Name).GetValue(array[i]);
            property.SetValue(obj, value);
        }
        list.Add(obj);
    }
    return list;
}

【问题讨论】:

  • 将鼠标悬停在红色波浪线上时,出现什么错误?
  • 另外,您的问题到底是什么?将数组转换为列表就像编写 var list = new List&lt;T&gt;(array); 一样简单,反之亦然 var array = list.ToArray();
  • 我建议你看看AutoMapper。非常方便的用于转换的 c# 库。
  • @EmonHaque 为什么不呢?

标签: c# .net wpf


【解决方案1】:

您可以为此使用大多数序列化程序,例如 Json.NET:

using Newtonsoft.Json;

...

internal static class MyConverter
{
    internal static T Convert<T>(object source)
    {
        string json = JsonConvert.SerializeObject(source);
        T result = JsonConvert.DeserializeObject<T>(json);
        return result;
    }
}

用法:

var s1 = new PerSecStruct { YQ = "1", EPS = 2, NAV = 3, Cash = 4, Debt = 5 };
// to object
var o = MyConverter.Convert<PerSec>(s1);
// back to struct
var s2 = MyConverter.Convert<PerSecStruct>(o);

【讨论】:

  • 使用 JSON 序列化程序与使用反射相比有什么好处?
  • @MindSwipe 对我来说 - 简单
【解决方案2】:

这可以使用反射来完成,尽管我也强烈建议您查看 AutoMapper 在 cmets 中指出的 Knoop,但如果您只需要这个结构,您可以编写自己的实现代码。

反射“是进程检查、内省和修改其自身结构和行为的能力”,在 C# 中,我们为此使用 System.Reflection 命名空间。

所以我们想将一个结构的所有公共字段映射到另一个结构的所有公共属性,为此我们首先需要获取结构上的所有公共字段和类上的所有属性,如下所示:

(为此我们需要每个实例)

var psStruct = ...;
var psClass =...;

// BindingFlags.Instance means all instance fields (non-static)
// BindingFlags.Public means only public fields
var structFields = typeof(PerSecStruct).GetFields(BindingFlags.Instance | BindingFlags.Public);

// The BindingFlags are the same, but we use 'GetProperties' because you declared properties not fields
var classFields = typeof(PerSec).GetProperties(BindingFlags.Instance | BindingFlags.Public);

所以现在我们有了一个包含结构体字段和类属性的列表,所以我们可以真正开始映射了:

// First filter the list on the Class to make sure we only have properties the struct has as well
classFields = classFields.Where(x => structFields.Select(y => y.Name).Contains(x.Name));

// ToDictionary to combine both lists into one
var fieldsDictionary = structFields.Zip(classFields, (k, v) => new {StructField = k, ClassField = v}).ToDictionary(x => x.StructField, x => x.ClassField);

foreach (var entry in fieldsDictionary)
{
    // Get the value from the psStruct object
    var value = entry.Key.GetValue(psStruct);
    // Set value on the psClass object (this can be a one-liner but I prefer this as it is more readable)
    entry.Value.SetValue(psClass, value);
}

如何使用这个的具体例子:

public static List<TTarget> As<TSource>(IEnumerable<TSource> source) where TTarget : new();
{
    var sourceFields = typeof(TSource).GetFields(BindingFlags.Instance | BindingFlags.Public);
    var targetProperties = typeof(TTarget).GetProperties(BindingFlags.Instance | BindingFlags.Public);

    var mapping = sourceFields.Zip(targetProperties, (k, v) => new {Source = k, Target = v}).ToDictionary(x => x.Source, x => x.Target);

    var retval = new List<TTarget>();
    foreach (var sourceObject in source)
    {
        var mappedObject = new TTarget();
        foreach (var m in mapping)
        {
            var value = entry.Key.GetValue(sourceObject);
            entry.Value.SetValue(mappedObject, value);
        }

        retval.Add(mappedObject);
    }

    return retval;
}

我添加了一个OrderBy 子句来获取结构/类字段/属性,因为否则结构/类中的顺序或声明必须相同,就像这样没关系。

目前这两种方式都不起作用,但如果您决定将结构中的字段替换为 Properties,您可以将 typeof(TSource).GetFields(...) 替换为 typeof(TSource).GetProperties(...),那么这两种方式都可以使用

【讨论】:

  • @EmonHaque 您在GetFieldsGetProperties 调用中缺少BindingFlags 参数
  • 即使我在GetFieldsGetProperties 调用中使用这些标志也不起作用
  • 现在我更清楚地阅读了代码,您正在阅读数组中空槽的类型。 2个问题:1)为什么不使用foreach?这将使阅读和理解代码变得更加容易。 2)你为什么要尝试将类对象列表转换为结构对象数组?
  • 还是不行!我已经编辑了最后一个编辑部分来展示我是如何使用你的方法的
  • @EmonHaque 查看我对映射实现的更新答案
猜你喜欢
  • 2018-05-01
  • 1970-01-01
  • 2017-07-05
  • 1970-01-01
  • 1970-01-01
  • 2016-12-05
  • 2011-04-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多