C# 如何在不进行手动类型转换的情况下调用 FieldInfo.SetValue 函数

C# How to call FieldInfo.SetValue function without manual type conversion

我想将数据-class 实例保存到数据库并从数据库加载它。我想自动生成 sql 命令。所以,我认为我需要使用 dictionary 来解决它。

参考我的老问题:How to convert 2D string array to 2D [int, double, bool, ..] array?

这样的工作流程。

  1. 将数据-class 实例转换为字典
  2. save/load字典to/fromtxt、数据库等
  3. 将字典转换为数据-class实例

我想我已经解决了这个问题。但我认为转换二维数组的方法仍然不是一个完美的方法。我想知道,当我调用 FieldInfo.SetValue 函数时,是否有另一种方法可以解决类型转换问题,而无需像我的解决方案那样使用 switch/case 状态。

帮助我简化代码。

数据class像这样

        public class DCylinderData
    {
        public int ID;

        public int[] Solenoid = new int[2];
        public int[] UpSensor = new int[DEF_MAX_CYLINDER_SENSOR];
        public int[] DownSensor = new int[DEF_MAX_CYLINDER_SENSOR];

        public double MovingTime;

        public ECylinderType CylinderType;
        public ESolenoidType SolenoidType;

        public bool[] boolTest = new bool[3];
        public string[] nameTest = new string[2];
        public int[,] TwoDimension = new int[3,4];

        public DCylinderData()
        {
        }
    }

主要代码如下

            // 0. initialize
        DCylinderData cylData = new DCylinderData();
        cylData.ID = 99;
        cylData.MovingTime = 1.1;
        cylData.CylinderType = ECylinderType.UPSTREAM_DOWNSTREAM;
        cylData.Solenoid = new int[]{ 2, 3};
        for (int i = 0; i < 2 ; i++)
        {
            cylData.Solenoid[i] = i + 2;
            cylData.nameTest[i] = $"NameTest_{i}";
        }
        for (int i = 0; i < DEF_MAX_CYLINDER_SENSOR; i++)
        {
            cylData.UpSensor[i] = i * 1;
            cylData.DownSensor[i] = i * 4;
        }
        for (int i = 0; i < cylData.TwoDimension.GetLength(0) ; i++)
        {
            for (int j = 0; j < cylData.TwoDimension.GetLength(1) ; j++)
            {
                cylData.TwoDimension[i, j] = i * cylData.TwoDimension.GetLength(1) + j;
            }
        }
        cylData.boolTest[0] = true;
        cylData.boolTest[1] = false;
        cylData.boolTest[2] = true;

        // 1. Class -> Dictionary
        Dictionary<string, string> fieldBook = new Dictionary<string, string>();

        Type type = typeof(DCylinderData);
        FieldInfo[] fields = type.GetFields();
        foreach (FieldInfo field in fields)
        {
            // 1.1 element
            if (field.FieldType.IsValueType)
            {
                fieldBook.Add(field.Name, field.GetValue(cylData).ToString());
            }
            // 1.2 array
            else if (field.FieldType.IsArray)
            {
                Array array = (Array)field.GetValue(cylData);

                // 1.2.1 1-D array
                if (array.Rank == 1)
                {
                    for (int i = 0; i < array.GetLength(0); i++)
                    {
                        fieldBook.Add($"{field.Name}__{i}", array.GetValue(i).ToString());
                    }
                }
                // 1.2.2 2-D array
                else if (array.Rank == 2)
                {
                    for (int i = 0; i < array.GetLength(0); i++)
                    {
                        for (int j = 0; j < array.GetLength(1); j++)
                        {
                            fieldBook.Add($"{field.Name}__{i}__{j}", array.GetValue(i, j).ToString());
                        }
                    }
                }
                else
                {
                    WriteLine($"Not support {field.Name}'s array {array.Rank} dimension.");
                }
            }
            else
            {
                WriteLine($"Not support to handle {field.Name}'s {field.FieldType.ToString()}");
            }
        }

        // 2. print Dictionary
        foreach (KeyValuePair<string, string> item in fieldBook)
        {
            WriteLine($"FieldBook {item.Key} : {item.Value}");
        }

        // 3. Dictionary -> Class
        DCylinderData copyData = new DCylinderData();
        foreach (FieldInfo field in fields)
        {
            // 3.1 handle element
            if (field.FieldType.IsValueType && fieldBook.ContainsKey(field.Name))
            {
                SetFieldValue(copyData, field, fieldBook[field.Name]);
            }
            // 3.2 handle array
            else if (field.FieldType.IsArray)
            {
                Array array = (Array)field.GetValue(copyData);
                string key, value;

                // 3.2.1 1-D array
                if (array.Rank == 1)
                {
                    var arr_1d = new string[array.GetLength(0)];
                    for (int i = 0; i < array.GetLength(0); i++)
                    {
                        key = $"{field.Name}__{i}";
                        value = fieldBook.ContainsKey(key) ? fieldBook[key] : "";
                        arr_1d.SetValue(value, i);
                    }
                    SetFieldValue(copyData, field, arr_1d);
                }
                // 3.2.1 2-D array
                else if (array.Rank == 2)
                {
                    var arr_2d = new string[array.GetLength(0), array.GetLength(1)];
                    for (int i = 0; i < array.GetLength(0); i++)
                    {
                        for (int j = 0; j < array.GetLength(1); j++)
                        {
                            key = $"{field.Name}__{i}__{j}";
                            value = fieldBook.ContainsKey(key) ? fieldBook[key] : "";
                            arr_2d.SetValue(value, i, j);
                        }
                    }
                    SetFieldValue(copyData, field, arr_2d);
                }
                else
                {
                    WriteLine($"Not support {field.Name}'s array {array.Rank} dimension.");
                }
            }
            // 3.3 not support
            else
            {
                WriteLine($"Not support to handle {field.Name}'s {field.FieldType.ToString()}");
            }
        }

        WriteLine("Press any key to continue");
        ReadLine();

和 SetFieldValue 类似这些的函数

        public static void SetFieldValue(Object target, FieldInfo fieldInfo, string value)
    {
        string fieldType = fieldInfo.FieldType.Name;
        fieldType = fieldType.ToLower();

        switch (fieldType)
        {
            case "boolean":
                bool b;
                fieldInfo.SetValue(target, bool.TryParse(value, out b) ? b : false);
                break;

            case "int32":
                int n;
                fieldInfo.SetValue(target, int.TryParse(value, out n) ? n : 0);
                break;

            case "double":
                double d;
                fieldInfo.SetValue(target, double.TryParse(value, out d) ? d : 0);
                break;

            case "string":
                fieldInfo.SetValue(target, value);
                break;
        }
    }

    public static void SetFieldValue(Object target, FieldInfo fieldInfo, string[] arr)
    {
        string fieldType = fieldInfo.FieldType.Name;
        fieldType = fieldType.ToLower();
        fieldType = fieldType.Replace("[]", "");

        switch (fieldType)
        {
            case "boolean":
                bool b;
                bool[] arr_b = Array.ConvertAll(arr, s => bool.TryParse(s, out b) ? b : false);
                fieldInfo.SetValue(target, arr_b);
                break;

            case "int32":
                int n;
                int[] arr_n = Array.ConvertAll(arr, s => int.TryParse(s, out n) ? n : 0);
                //int[] arr_n1 = Array.ConvertAll(arr, int.Parse);
                //int[] arr_n2 = arr.Select(s => int.TryParse(s, out n) ? n : 0).ToArray();
                fieldInfo.SetValue(target, arr_n);
                break;

            case "double":
                double d;
                double[] arr_d = Array.ConvertAll(arr, s => double.TryParse(s, out d) ? d : 0);
                fieldInfo.SetValue(target, arr_d);
                break;

            case "string":
                fieldInfo.SetValue(target, arr);
                break;
        }
    }

    public static void SetFieldValue(Object target, FieldInfo fieldInfo, string[,] arr)
    {
        string fieldType = fieldInfo.FieldType.Name;
        fieldType = fieldType.ToLower();
        fieldType = fieldType.Replace("[,]", "");

        // 0. string return
        switch (fieldType)
        {
            case "string":
                fieldInfo.SetValue(target, arr);
                return;
                break;
        }

        // 1. initialize
        int n;
        double d;
        bool b;

        //object[,] output = new object[arr.GetLength(0), arr.GetLength(1)];
        int[,] output_n = new int[arr.GetLength(0), arr.GetLength(1)];
        bool[,] output_b = new bool[arr.GetLength(0), arr.GetLength(1)];
        double[,] output_d = new double[arr.GetLength(0), arr.GetLength(1)];

        // 2. convert
        for (int i = 0; i < arr.GetLength(0); i++)
        {
            for (int j = 0; j < arr.GetLength(1); j++)
            {
                switch (fieldType)
                {
                    case "boolean":
                        output_b[i, j] = bool.TryParse(arr[i, j], out b) ? b : false;
                        break;

                    case "int32":
                        output_n[i, j] = int.TryParse(arr[i, j], out n) ? n : 0;
                        break;

                    case "double":
                        output_d[i, j] = double.TryParse(arr[i, j], out d) ? d : 0;
                        break;
                }
            }
        }

        // 2. setvalue
        //fieldInfo.SetValue(target, output);
        switch (fieldType)
        {
            case "boolean":
                fieldInfo.SetValue(target, output_b);
                break;

            case "int32":
                fieldInfo.SetValue(target, output_n);
                break;

            case "double":
                fieldInfo.SetValue(target, output_d);
                break;
        }
    }

这都是我的代码的一部分。

您将需要 GetElementTypeArray.CreateInstanceTypeDescriptor.GetConverterdynamic(可选)。您仍然需要相当多的代码来完成它,因为您需要处理异常和未知类型以及字符串中的错误。

要回答您的问题,您可以做 3 件事:

将以string fieldType = fieldInfo.FieldType.Name;开头的3行替换为一行:string fieldType = fieldInfo.FieldType.GetElementType().Name;

然后将您的一维数组输出替换为:

dynamic output = Array.CreateInstance(fieldInfo.FieldType.GetElementType(), arr.GetLength(0));

你的二维数组输出:

dynamic output = Array.CreateInstance(fieldInfo.FieldType.GetElementType(), arr.GetLength(0), arr.GetLength(1));

但这就是它变得棘手的地方:您需要从 fieldInfo 创建一个 "type converter" 函数并在您的 for 循环中使用它。但是您不能 'unbox' 并在不知道要转换为哪种类型的情况下反对所需的类型。所以不幸的是,您将需要 case 语句(或类似的东西)。基本上数组是问题所在,尽管现在您只需要 1 个 switch 语句:

var converter = TypeDescriptor.GetConverter(fieldInfo.FieldType.GetElementType());
for (int i = 0; i < arr.GetLength(0); i++)
{
    for (int j = 0; j < arr.GetLength(1); j++)
    {
        switch (fieldType)
        {
            case "Int32":
                output[i,j] = (int)converter.ConvertFromString(arr[i,j]);
                break;
            ...
        }
    }
}

fieldInfo.SetValue(target, output);

对于本质上是序列化的内容来说,付出了如此多的努力。这是一个更好的解决方案:

  1. 首先从 NuGet 获取 Json.NET 到您的项目中
  2. // 1. Class -> Dictionary 部分替换为 string output = JsonConvert.SerializeObject(cylData);
  3. 要取回 class:JsonConvert.DeserializeObject<DCylinder>(output);

两行代码解决:)


任何其他内容,您应该 post 在 Code Review.