我可以使用泛型来推断已知类型的实际类型吗?

时间:2013-10-10 16:18:46

标签: c# json generics serialization reflection

我想做什么:使用Activator动态创建一个对象(可以是任何类型)并将其传递给序列化JSON的方法。注意:我已经查看了No type inference with generic extension method但是这并没有给我任何有关如何解决这个问题的有用信息。

编辑:使用.NET 3.5 Framework

问题:我收到的对象可能是一个数组(例如int []),当我使用泛型来键入接收到的对象时,我得到一个对象[]而不是int []。

代码示例:

class Program
{
    static void Main(string[] args)
    {
        object objArr = new int[0];
        int[] intArr = new int[0];
        string arrS = "[1,2]";

        object objThatIsObjectArray = Serialize(objArr, arrS);//I want this to evaluate as int[]
        object objThatIsIntArray = Serialize(intArr, arrS);//this evaluates as int[]

        Console.Read();
    }
    public static object Serialize<T>(T targetFieldForSerialization, string value)
    {
        return value.FromJson<T>();
    }

}

public static class JSONExtensions
{
    public static TType FromJson<TType>(this string json)
    {
        using (var ms = new MemoryStream(Encoding.Default.GetBytes(json)))
        {
            var ser = new DataContractJsonSerializer(typeof(TType));
            var target = (TType)ser.ReadObject(ms);

            ms.Close();

            return target;
        }
    }
}

3 个答案:

答案 0 :(得分:5)

没有自动的方法,如果您“认为”某个对象可能是int[],您可以尝试使用as进行投射并检查结果是否为空;

static void Main(string[] args)
{
    object objArr = new int[0];
    string arrS = "[1,2]";

    int[] testArr = objArr as int[];

    if(testArr != null)
        object objThatIsIntArray = Serialize(testArr, arrS);//this evaluates as int[]
    else
        object objThatIsObjectArray = Serialize(objArr, arrS); //this evaluates as object because it could not figure out what it was.

    Console.Read();
}

如果您知道该类型只能是少数选择中的一种,您可以将其与其他if测试链接起来,每种类型一个。

这种模式在处理接口时非常常见,例如,这里是LINQ的Count()方法在内部实现的方式,它检查类是否实现ICollection<TSource>ICollection所以它可以使用该接口的Count属性。

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    ICollection<TSource> collection = source as ICollection<TSource>;
    if (collection != null)
    {
        return collection.Count;
    }
    ICollection collection2 = source as ICollection;
    if (collection2 != null)
    {
        return collection2.Count;
    }
    int num = 0;
    checked
    {
        using (IEnumerator<TSource> enumerator = source.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                num++;
            }
        }
        return num;
    }
}

另一个选项是使用.GetType()来获取Type并将其传递给而不是隐式检测它,尽管我不确切知道如何处理FromJson的返回类型我的头顶。

class Program
{
    static void Main(string[] args)
    {
        object objArr = new int[0];
        int[] intArr = new int[0];
        string arrS = "[1,2]";

        object objThatIsObjectArray = Serialize(objArr.GetType(), arrS);//this evaluates as int[]
        object objThatIsIntArray = Serialize(intArr.GetType(), arrS);//this evaluates as int[]

        Console.Read();
    }
    public static object Serialize<T>(Type type, string value)
    {
        return value.FromJson(type);
    }

}

public static class JSONExtensions
{
    public static object FromJson(this string json, Type type)
    {
        using (var ms = new MemoryStream(Encoding.Default.GetBytes(json)))
        {
            var ser = new DataContractJsonSerializer(type);
            var target = ser.ReadObject(ms);

            ms.Close();

            return target;
        }
    }
}

答案 1 :(得分:2)

预警:这个答案描述的技术可能不是基于您的示例代码的最佳选择,但了解类似情况很有用。

如果要基于运行时类型绑定到适当的方法,C#4.0+可以做到这一点(dynamic类型)。在您的情况下,您希望基于第一个参数绑定类型参数T,因此只需将类型为dynamic的值作为第一个参数传递:

class Program
{
    static void Main(string[] args)
    {
        dynamic objArr = new object[0];
        dynamic intArr = new int[0];
        int[] typedIntArr = new int[0];
        string arrS = "[1,2]";

        Serialize(objArr, arrS); // dynamic call site
        Serialize(intArr, arrS); // dynamic call site
        Serialize(typedIntArr, arrS); // regular static call
    }

    public static object Serialize<T>(T targetFieldForSerialization, string value)
    {
        Console.WriteLine("Type: " + typeof(T).Name);
        return value.FromJson<T>();
    }
}

使用动态参数调用Serialize时,编译器将发出动态调用站点。现在,这是什么意思?

动态调用站点根据参数的运行时类型(以及可能的预期返回类型)评估调用并绑定到适当的方法。当调用完成后,绑定器将查看参数,检查它们的实际类型,然后确定要调用哪个方法,在泛型方法的情况下,确定要传递的类型参数。结果在我上面的代码片段的输出中很明显:

Type: Object[]
Type: Int32[]
Type: Int32[]

你可能认为这听起来像是一个非常重要的操作,而且确实如此。绑定器必须应用标准C#绑定规则来解析正确的方法。在知道所涉及的所有运行时类型之前,它通常甚至不能知道所有可能的候选方法。幸运的是,.NET中的动态调用站点通常不会为每个调用完成整个过程。动态调用站点记住过去调用的细节:当调用发生时,调用站点将根据过去的参数类型组合检查当前参数类型,如果找到匹配,它将调用之前调用的相同方法(使用相同的泛型类型参数)。这些检查和目标方法调用都会被编译,这有助于提高性能,您可以从JIT优化中受益。

现在,调用网站的缓存(它的“内存”)效果如何?这取决于参数类型的变化频率,以及它在整个生命周期中遇到的不同组合。动态语言运行时使用三个级别的缓存,因此在大多数情况下,您可以获得相当可观的性能 - 不是静态类型所能获得的,但可能比在每次调用时使用反射更好。在大多数情况下,调用网站最终将构建规则,如果您自己编写规则,则看起来像这样:

__Serialize(/* object[] */ objArr, arrS);
__Serialize(/* int[] */ intArr, arrS);
Serialize(typedIntArr, arrS);

...

private static object __Serialize(object arg1, string arg2) {
    // These rules get updated as the type of 'arg1' changes:
    if (arg1 is object[]) { 
        return Serialize<object[]>((object[])arg1, arg2);
    }
    else if (arg1 is int[]) {
        return Serialize<int[]>((int[])arg1, arg2);
    }
    else {
        // 1) Figure out the right method to call based on the type of 'arg1'
        // 2) Update this set of rules
        // 3) Call the newly bound method and return its result
    }
}

所以,这一切都很吸引人,但这是你最好的选择吗?基于您问题中的示例代码,可能不是。为什么?在您的示例中,看起来您拥有TType泛型参数的唯一原因是您可以捕获相应的Type并将其用于反射(或者更确切地说,DataContractJsonSerializer可以用它来反射)。当然,最简单的方法是在参数上调用GetType(),这就是为什么Scott的第二个例子是这个特定情况的理想解决方案。如果你实际上不需要浪费动态绑定的开销是没有意义的(注意他完全删除了泛型参数)。但是,在某些时候,您可能会发现自己处于类似的情况,您真正可以从动态绑定中受益,当这个时间到来时,这些信息应该证明是有用的。

答案 2 :(得分:1)

我不确定我是否理解你的问题。我将假设您要做的是反序列化某些表示某种对象的数组的json,并且您希望将此反序列化的输出强制类型化为T类型的数组。

这意味着你在使用Deserialize方法时已经知道了type-argument应该是什么。如果您当时不知道这一点,系统如何提供强类型数组。毕竟,你必须要求它接收它。否则你会被那个对象数组困住。

我没有得到的是为什么你要传递第一个参数; Type type,你们已经知道泛型参数的类型。所以我将删除类型参数,给我这样的东西:

所以这意味着:

public static T[] Deserialize<T>(string value)
{
    return value.FromJson(value, typeof(T)) as T;
}

public static T[] DeserializeArray<T>(string value)
{
    return Deserialize<T[]>(value);
}

并称之为:

int myInt = Deserialize<int>("1234");
int[] myIntArray1 = Deserialize<int[]>("[1, 2, 3, 4, 5]");
int[] myIntArray2 = DeserializeArray<int>("[1, 2, 3, 4, 5]");

我无法从您的代码或问题中看到它,因为我不知道我必须猜测的序列化器,但是如果由于retreiving对象[]而遇到反序列化对象时遇到问题那么你可能想用LINQ扩展来解决这个问题。

int[] myIntArray = new object[]{1,2}.OfType<int>().ToArray():

PS:据我理解术语,您可以将clr对象序列化为json,或将json反序列化为clr对象。所以你的问题是关于反序列化而不是序列化(这是你正在使用的词),如果我理解正确的话。?