5

What I want to do: Use Activator to dynamically create an object (which could be any type) and pass it to a method for serializing JSON. Note: I have already looked at No type inference with generic extension method but that didn't really give me any useful information on how I could solve this problem.

Edit: Using .NET 3.5 Framework

Problem: The object I receive could be an array (such as int[]) and when I use generics to type the received object, I get an object[] instead of an int[].

Code sample:

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

3 回答 3

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()获取类型并将其传入而不是隐式检测到它,尽管我不知道如何处理返回类型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;
        }
    }
}
于 2013-10-10T16:28:50.533 回答
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可以将其用于反射)。当然,最直接的方法是调用论点,这就是为什么 Scott 的第二个示例是这种特殊情况GetType()的理想解决方案. 如果您实际上不需要它,那么浪费动态绑定的开销是没有意义的(请注意,他完全删除了泛型参数)。但是,在某些时候,您可能会发现自己处于类似的情况,您确实可以从动态绑定中受益,并且当那个时候到来时,这些信息应该证明是有用的。

于 2013-10-10T18:28:51.680 回答
1

我不确定我是否正确理解了您的问题。我将假设您要做的是反序列化一些表示某种对象数组的 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。所以你的问题是关于反序列化而不是序列化(这是你使用的词),如果我理解正确的话..?

于 2013-10-10T17:27:56.910 回答