0

I want to have two converters like these:

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

where T1 is a class and T2 is a struct. Both the class and struct members have Identical names and types. With the above I get red squigly in first method's list.Add(array[i]) and second methods array[i] = list[i] so these don't work. What's the easiest way to do that?

EDIT

Here's the class:

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
}

and here's the struct:

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

EDIT

In second method, I've these now:

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

this entry.Key.SetValue(psStruct, value); line isn't working so the elements of array have their default values (null/0).

EDIT

It works if I use __makeref(array[i]) as noted here by petelids. With that I can do this:

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

I don't need those Binding Flags! And to convert back to List I've to do this in other method:

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;
}
  • When hovering the mouse over the red squiggly line, what error do you get? – MindSwipe Dec 16 '19 at 06:49
  • 1
    Also, what exactly is your question? Converting an array to a list is as easy as writing `var list = new List(array);` and vice versa it's `var array = list.ToArray();` – MindSwipe Dec 16 '19 at 06:51
  • @MindSwipe, edited the post to provide more info. In the first method `cannot convert T2 to T1` and in the second `Cannot implicitly convert T1 to T2` –  Dec 16 '19 at 06:54
  • 5
    I would advise you to take a look at `AutoMapper`. Very handy c# library for conversion. –  Dec 16 '19 at 06:59
  • Why generics? What is, if `T1` is `string` and `T2` is `int`? I wouldn't do it with generics. There are several ways how to make a conversion `T1` to `T2`: Non generic converter (safe), persisting, `unsafe` conversion or a reflection. – Rekshino Dec 16 '19 at 08:37
  • 1
    Does this answer your question? [cast class into another class or convert class to another](https://stackoverflow.com/questions/3672742/cast-class-into-another-class-or-convert-class-to-another) – Rekshino Dec 16 '19 at 08:45
  • @Rekshino, does that work for struct? –  Dec 16 '19 at 09:56
  • 1
    @EmonHaque Why not? – Rekshino Dec 16 '19 at 12:03
  • @Rekshino, Ok I'll try that as well. –  Dec 16 '19 at 12:16
  • @Rekshino, It looks like that method is doing something similar to what I've done in last Edit part. Is there any benefit of using that over this? –  Dec 16 '19 at 12:18

2 Answers2

1

You could use most of serializers for that, for example with 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;
    }
}

usage:

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);
MindSwipe
  • 7,193
  • 24
  • 47
sarh
  • 6,371
  • 4
  • 25
  • 29
0

This can be done using Reflection, although I also highly advise you look at AutoMapper as Knoop pointed out in the comments, but if you only need it for this one struct you could code your own implementation.

Reflection "is the ability of a process to examine, introspect, and modify its own structure and behaviour", in C# we use the System.Reflection namespace for this.

So we want to map all public fields of one struct to all public properties of another, for this we first need to get all public fields on our struct and all the properties on our class like so:

(For this we need an instance of each)

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

So now we have a list of both the fields on the struct and the Properties on the class, so we can actually begin mapping:

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

A concrete example of how to use this:

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

I added an OrderBy clause to getting the struct/ class fields/ Properties because else the order or declaration inside the struct/ class would have had to have been the same, like this it doesn't matter.

This currently does not work both ways, but if you decide to replace the fields inside your struct with Properties you can replace the typeof(TSource).GetFields(...) with typeof(TSource).GetProperties(...), then it would work both ways

MindSwipe
  • 7,193
  • 24
  • 47
  • @EmonHaque you're missing the `BindingFlags` parameter in your `GetFields` and `GetProperties` call – MindSwipe Dec 16 '19 at 09:01
  • It doesn't work even if i use those flags in `GetFields` and `GetProperties` call –  Dec 16 '19 at 09:03
  • 1
    Now that I read the code more it's clear, your reading the type of an empty slot in the array. 2 questions: 1) Why not use foreach? It would make reading and understanding the code much easier. 2) Why are you trying to convert a List of class objects to an array of struct objects? – MindSwipe Dec 16 '19 at 09:06
  • It still isn't working! I've edited the last edit part to show how I used your approach –  Dec 16 '19 at 09:55
  • It also doesn't work If I do `var psStruct = new T2()` in the beginning of for loop and set `array[i] = psStruct` at the end. –  Dec 16 '19 at 10:07
  • 1
    @EmonHaque see my updated answer for an implementation of the mapping – MindSwipe Dec 16 '19 at 11:25
  • That's great for `class` to `class` mapping BUT to mutate struct I think we need that `__makeref`, right? –  Dec 16 '19 at 11:36