用于结构到类的通用转换器,反之亦然

时间:2019-12-16 06:47:02

标签: c# .net wpf

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

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是一个结构。 class和struct成员都具有相同的名称和类型。有了上面的内容,我在第一种方法的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)。

编辑

如果我使用{strong>象鼻猴指出的here,则可以使用__makeref(array[i])。这样我就可以做到:

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

2 个答案:

答案 0 :(得分:2)

您可以为此使用大多数序列化器,例如使用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);

答案 1 :(得分:1)

这可以使用反射来完成,尽管我也极力建议您像评论中指出的Knoop那样看待AutoMapper,但是如果只需要这一结构,则可以自己编写实施。

反射“是过程检查,自省和修改其自身结构和行为的能力”,在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子句以获取struct /类的fields / Properties,因为否则struct /类内的顺序或声明必须相同,就像这样,没关系。 / p>

目前这不是双向的,但是如果您决定用“属性”替换结构体中的字段,则可以将typeof(TSource).GetFields(...)替换为typeof(TSource).GetProperties(...),那么它将双向兼容。