【问题标题】:Can I use generics to infer actual type over known type?我可以使用泛型来推断已知类型的实际类型吗?
【发布时间】:2013-10-18 12:40:48
【问题描述】:

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

编辑:使用 .NET 3.5 框架

问题:我收到的对象可能是一个数组(例如 int[]),当我使用泛型键入收到的对象时,我得到的是 object[] 而不是 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;
        }
    }
}

【问题讨论】:

    标签: c# json generics serialization reflection


    【解决方案1】:

    没有自动的方法可以做到这一点,如果您“认为”一个对象可能是 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&lt;TSource&gt;ICollection 以便它可以使用该接口的 @987654328 @属性。

    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() 获取类型并将其传入而不是隐式检测到它,尽管我不知道如何从我的顶部处理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;
            }
        }
    }
    

    【讨论】:

    • 我想处理任何类型的集合,因此添加大量 IF 块会使代码令人不快且难以管理。
    • @JAMSUPREME 我添加了第二个解决方案,但是您丢失了 FromJason 中的强类型返回对象
    • 在我的特殊情况下,我实际上并不需要强类型返回对象,因此您的第二个解决方案实际上效果很好。
    【解决方案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 时,编译器将发出一个动态调用站点。现在,这是什么意思?

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

    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 的第二个示例是这种特殊情况的理想解决方案。如果您实际上不需要它,那么浪费动态绑定的开销是没有意义的(请注意,他完全删除了泛型参数)。但是,在某些时候,您可能会发现自己处于类似的情况,您确实可以从动态绑定中受益,而到那时,这些信息应该会很有用。

    【讨论】:

    • 感谢您的评论。在我的特殊情况下,我仅限于 3.5 框架,因此我考虑使用超出范围的动态。
    • 是的,除非您的目标是 .NET 4+,否则您将无法利用 dynamic 关键字。您仍然可以引用 DLR 并手动构建动态调用站点(跳过编译器中间人),但我怀疑在很多情况下这样做是值得的 :)。
    【解决方案3】:

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

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

    我不明白你为什么要传入第一个参数; Type 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]");
    

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

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

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

    【讨论】:

    • 我传递对象的原因是因为 T 将评估为“object”而不是“int[]”,这就是问题所在。因为我使用 Activator 创建一个对象,所以它会返回一个“object”类型的“int[]”。这是否说明了为什么您提出的解决方案对我不起作用?
    • 所以基本上,我的前提是你知道 T 在消费时间是什么是错误的。在那种情况下,我同意迈克的回答。虽然我很不喜欢尝试的类型列表,但有时别无选择。我不确定我是否理解您将如何让自己陷入这种情况,即您拥有正确类型的对象,但在编译时不知道该类型是什么。但我敢肯定,你有一个复杂的商业案例,我真的不需要知道.. ;)
    • 我应该补充一点,实际上可以使用反射来动态调用带有类型参数的方法。看到这个:stackoverflow.com/questions/232535/…
    猜你喜欢
    • 1970-01-01
    • 2020-06-09
    • 1970-01-01
    • 1970-01-01
    • 2022-11-07
    • 1970-01-01
    • 2019-09-17
    • 1970-01-01
    相关资源
    最近更新 更多