当我传递HashSet

时间:2018-08-18 06:51:47

标签: c# generics reflection

所以我试图弄清楚为什么我的通用方法会丢失类型信息。

当我传递反射数据(在此示例中,T = HashSet)时,代码认为T = system.object。当我传递非反射数据时,我的T正确键入为HashSet类型。

我的示例中唯一的区别是,在我的第一个调用中,我直接传递了一个HashSet,而在第二个调用中,我传递了一个具有HashSet属性的对象,并使用反射来获取HashSet并将其传递。

两个HashSet都由完全相同的代码执行,但是T每次都不同。

我设法破解了一个“变通方法”,在该方法中,我传入了第二个参数(正确的类型),然后从那里可以得到想要的结果。

但是,这似乎很棘手,我想知道为什么我必须执行此替代方法才能获得所需的结果。

有人可以解释为什么我观察我所观察的内容,以及如何每次都能正确地输入我的T型,而无需两次输入该类型吗?

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

public class Program
{
    public static void Main()
    {
        var simple = new HashSet<string>();
        simple.Add("1");
        simple.Add(" 1 ");
        Console.WriteLine("Simple Before => " + JsonConvert.SerializeObject(simple));
        simple = simple.DeepTrimAll();
        Console.WriteLine("Simple After => " + JsonConvert.SerializeObject(simple));
        Console.WriteLine();
        var complex = new Complex();
        complex.HS = new HashSet<string>();
        complex.HS.Add("1");
        complex.HS.Add(" 1 ");
        Console.WriteLine("Complex Before => " + JsonConvert.SerializeObject(complex));
        complex = complex.DeepTrimAll();
        Console.WriteLine("Complex After => " + JsonConvert.SerializeObject(complex));
    }
}

public class Complex
{
    public HashSet<string> HS
    {
        get;
        set;
    }
}

public static class Ext
{
    public static T DeepTrimAll<T>(this T context, Type t)where T : class
    {
        if (context is IEnumerable<string>)
        {
            var type = typeof (T);
            Console.WriteLine("T = " + type.ToString() + " , t = " + t.ToString());
            var list = new List<string>(context as IEnumerable<string>);
            for (int i = 0; i < list.Count(); i++)
            {
                list[i] = list[i].Trim();
            }

            var res = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(list), t);
            return (T)res;
        }

        var properties = typeof (T).GetProperties().Where(x => x.CanRead && x.CanWrite).ToList();
        foreach (var propertyInfo in properties)
        {
            var propType = propertyInfo.PropertyType;
            var value = propertyInfo.GetValue(context, null);
            if (propType == typeof (string))
            {
                if (!string.IsNullOrWhiteSpace(value.ToString()))
                {
                    propertyInfo.SetValue(context, value.ToString().Trim());
                }
            }
            else if (!propType.IsEnum && !propType.IsPrimitive)
            {
                var newValue = value.DeepTrimAll(propType);
                propertyInfo.SetValue(context, newValue);
            }
        }

        return context;
    }

    public static T DeepTrimAll<T>(this T context)where T : class
    {
        return context.DeepTrimAll(typeof (T));
    }
}

此示例产生以下结果:

Simple Before => ["1"," 1 "]
T = System.Collections.Generic.HashSet`1[System.String] , t = System.Collections.Generic.HashSet`1[System.String]
Simple After => ["1"]

Complex Before => {"HS":["1"," 1 "]}
T = System.Object , t = System.Collections.Generic.HashSet`1[System.String]
Complex After => {"HS":["1"]}

dotNetFiddle

1 个答案:

答案 0 :(得分:2)

类型参数是在编译时而不是在执行时推断出来的(通常-稍后再见)。

致电complex.DeepTrimAll()时,等同于:

Ext.DeepTrimAll<Complex>(complex);

然后调用DeepTrimAll<Complex>(complex, typeof(Complex))

现在Complex尚未实现IEnumerable<string>,因此代码进入了else语句的if分支,该语句以递归方式调用DeepTrimAll,例如这个:

var propType = propertyInfo.PropertyType;
var value = propertyInfo.GetValue(context, null);
...
var newValue = value.DeepTrimAll(propType);

现在value的编译时类型为object,因此类型推断会在编译时插入 并将其转换为:

var newValue = DeepTrimAll<object>(value, propType);

这就是为什么要获得输出的原因。

现在,您可以在此处改用动态键入:

var propType = propertyInfo.PropertyType;
dynamic value = propertyInfo.GetValue(context, null);
...
var newValue = DeepTrimAll(value, propType);

不再使用DeepTrimAll作为扩展方法,因为动态类型不能处理扩展方法。但是现在类型推断是动态执行的,因此它将调用DeepTrimAll<HashSet<string>>(value, propType)

尽管,我建议这样做-我可能会将您的DeepTrimAll(T context, Type t)更改为DeepTrimAll(object context, Type t),并且始终使用{{1} },而不是t