samedi 18 août 2018

Why does my generic type method think T is system.object when I'm passing in a HashSet

So I'm trying to figure out why my generic method is losing type information.

When I pass in reflected data (in this example T = HashSet) the code thinks T = system.object. When I pass in non-reflected data, my T is correctly typed as HashSet type.

The only difference in my example is that in my first call I am passing directly a HashSet, and in my second call I am passing in an object with a HashSet property, and using reflection to get the HashSet and pass it in.

Both HashSets get executing by the exact same code, yet T is different each time.

I've managed to hack together a "workaround" where I pass in as a second parameter, the correct type, and from there I can get my desired results.

However, this seems hacky and I'd like to know why I have to do this workaround to get my desired results.

Can anybody explain why I am observing what I'm observing and how I can get my T type to be correct each and every time without passing in the type twice?

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

This example produces the following results:

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





Aucun commentaire:

Enregistrer un commentaire