简体   繁体   English

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

[英]Can I use generics to infer actual type over known type?

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. 我想做的事:使用Activator动态创建一个对象(可以是任何类型)并将其传递给序列化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 编辑:使用.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[]. 问题:我收到的对象可能是一个数组(例如int []),当我使用泛型来键入接收到的对象时,我得到一个对象[]而不是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;
        }
    }
}

There is no automatic way to do it, if you "think" a object may be a int[] you could try casting it with as and checking if the result is null; 没有自动的方法来做,如果你“认为”一个对象可能是一个int[]你可以尝试使用as转换as并检查结果是否为null;

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 you know the type could be only one of a few choices you can chain it with other if tests, one for each type. 如果您知道类型可能只是少数几种选择中的一种,您可以将其与其他if测试链接起来,每种类型一种。

This pattern is very common when dealing with interfaces, for example here is how LINQ's Count() method is implemented internally, it checks to see if the class implements ICollection<TSource> or ICollection so it can use that interface's Count property. 这种模式在处理接口时非常常见,例如,这里是如何在内部实现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;
    }
}

Another option is use .GetType() to get the Type and just pass that in instead of having it be implicitly detected, although I don't know exactly how to handle the return type for FromJson off of the top of my head. 另一种选择是使用.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;
        }
    }
}

Forewarning : this answer describes a technique that probably isn't the best choice based on your example code, but it's useful to know for similar situations. 预警 :这个答案描述的技术可能不是基于您的示例代码的最佳选择,但了解类似情况很有用。

If you want to bind to the appropriate method based on the runtime type, C# 4.0+ has facilities to do just that (the dynamic type). 如果要基于运行时类型绑定到适当的方法,C#4.0+可以实现这一功能( dynamic类型)。 In your case, you want to bind the type argument T based on the first argument, so simply pass a value typed as dynamic as the first argument: 在您的情况下,您希望基于第一个参数绑定类型参数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>();
    }
}

When Serialize is called with a dynamic argument, the compiler will emit a dynamic call site. 使用动态参数调用Serialize ,编译器将发出动态调用站点。 Now, what does that mean? 这是什么意思?

A dynamic call site evaluates a call and binds to the appropriate method based on the runtime types of the arguments (and possibly the expected return type). 动态调用站点根据参数的运行时类型 (以及可能的预期返回类型)计算调用并绑定到适当的方法。 When the call gets made, the binder will take a look at the arguments, check their actual types, and then determine which method to call and, in the case of generic methods, which type arguments to pass. 当调用完成后,绑定器将查看参数,检查它们的实际类型,然后确定要调用哪个方法,对于泛型方法,确定要传递的类型参数。 The results are evident in the output from my code snippet above: 结果在我上面的代码片段的输出中很明显:

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

You may think that this sounds like a non-trivial operation, and it is. 您可能认为这听起来像是一个非平凡的操作,而且确实如此。 The binder has to apply standard C# binding rules to resolve the correct method. 绑定器必须应用标准C#绑定规则来解析正确的方法。 It often cannot even know all of the possible candidate methods to consider until it knows all of the runtime types involved. 在知道所涉及的所有运行时类型之前,它通常甚至不能知道所有可能的候选方法。 Fortunately, dynamic call sites in .NET typically don't go through this whole process for every call. 幸运的是,.NET中的动态调用站点通常不会为每个调用完成整个过程。 Dynamic call sites remember the details of past invocations: when the call happens, the call site will check the current argument types against past combinations of argument types, and if it finds a match, it will call the same method it called before (with the same generic type arguments). 动态调用站点记住过去调用的细节:当调用发生时,调用站点将根据过去的参数类型组合检查当前参数类型,如果找到匹配,它将调用之前调用的相同方法(使用相同的泛型类型参数)。 Both these checks and the target method call get compiled, which helps with performance, and you may benefit from JIT optimizations. 这些检查和目标方法调用都会被编译,这有助于提高性能,您可以从JIT优化中受益。

Now, how effective is the call site's caching (it's "memory")? 现在,呼叫站点的缓存(它的“内存”)效果如何? That depends on how often the argument types change, and how many different combinations it encounters throughout its lifetime. 这取决于参数类型的变化频率,以及它在整个生命周期中遇到的不同组合。 The dynamic language runtime utilizes three levels of caching, so in most cases you get pretty respectable performance--not quite what you would get with static typing, but probably better than using reflection on every call. 动态语言运行时使用三个级别的缓存,因此在大多数情况下,您可以获得相当可观的性能 - 不是静态类型所能获得的,但可能比在每次调用时使用反射更好。 In most cases, the call site will end up constructing rules that, if you were to code them yourself, would look something like this: 在大多数情况下,调用站点最终将构建规则,如果您自己编写它们,则看起来像这样:

__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
    }
}

So, this has all been fascinating, but is this your best option here? 所以,这一切都很吸引人,但这是你最好的选择吗? Based on the example code in your question, probably not. 基于您问题中的示例代码,可能不是。 Why? 为什么? In your example, it looks like the only reason you have the TType generic parameter is so that you can capture the corresponding Type and use it for reflection (or, rather, so DataContractJsonSerializer can use it for reflection). 在您的示例中,看起来您拥有TType泛型参数的唯一原因是您可以捕获相应的Type并将其用于反射(或者更确切地说, DataContractJsonSerializer可以将其用于反射)。 The most straightforward way of doing this is, of course, to just call GetType() on the argument, which is why Scott's second example is an ideal solution for this particular case . 当然,最简单的方法是在参数上调用GetType() ,这就是为什么Scott的第二个例子是这个特定情况的理想解决方案。 There's no sense in wasting the overhead of dynamic binding if you don't actually need it (note that he removed the generic parameter entirely). 如果你实际上不需要浪费动态绑定的开销是没有意义的(注意他完全删除了泛型参数)。 At some point, however, you may find yourself in a similar situation where you really could benefit from dynamic binding, and when that time comes, this information should prove useful. 但是,在某些时候,您可能会发现自己处于类似的情况,您真正可以从动态绑定中受益,当这个时候到来时,这些信息应该证明是有用的。

I'm not sure that I understand your question correctly. 我不确定我是否理解你的问题。 I'm going to assume that what you want to do is deserialize some json representing arrays of some sort of object, and you want the output of this deserialization to be stronly typed as arrays of the type T. 我将假设您要做的是反序列化某些表示某种对象的数组的json,并且您希望将此反序列化的输出强制类型化为T类型的数组。

This means that you allready know, when consuming the Deserialize-method what the type-argument should be. 这意味着你在使用Deserialize方法时已经知道了type-argument应该是什么。 If you don't know this at that time how can the system provide a strongly typed array. 如果您当时不知道这一点,系统如何提供强类型数组。 After all, you must ask for it to receive it. 毕竟,你必须要求它接收它。 Otherwise you'd be stuck with that object array. 否则你会被那个对象数组困住。

What I don't get is why you want to pass in that first parameter; 我没有得到的是为什么你要传递第一个参数; Type type , you allready know the type from the generic argument. Type type ,你已经知道泛型参数的类型。 So I'll remove the type argument, leaving me with something like this: 所以我将删除类型参数,留下我这样的东西:

So that means something like this: 所以这意味着:

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

And call it like this: 并称之为:

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

I can't see it from your code or question, and since I don't know the serializer that intimately I'd have to guess, but if you're having trouble with the deserialized object because of retreiving object[]'s then you might want to use a LINQ extension to solve that. 我无法从你的代码或问题中看到它,因为我不知道我必须猜测的序列化器,但是如果你因为retreiving对象[]而遇到反序列化对象的问题那么您可能希望使用LINQ扩展来解决此问题。

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

PS: As I understand the terminology, you would serialize your clr-objects to json, or deserialize json into clr-objects. PS:据我理解术语,你可以将clr-objects序列化为json,或者将json反序列化为clr-objects。 So your question is about deserialization rather than serialization (which is the word you're using), if I understand correctly..? 所以你的问题是关于反序列化而不是序列化(这是你正在使用的词),如果我理解正确的话......?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM