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;
}
}
}
Type inference represents the Java compiler's ability to look at a method invocation and its corresponding declaration to check and determine the type argument(s). The inference algorithm checks the types of the arguments and, if available, assigned type is returned.
Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable.
Java SE 7 supports limited type inference for generic instance creation; you can only use type inference if the parameterized type of the constructor is obvious from the context. For example, the following example does not compile: List<String> list = new ArrayList<>(); list.
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;
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.
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.
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.
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). 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:
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. 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. 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. 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.
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). 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. 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.
This means that you allready know, when consuming the Deserialize-method what the type-argument should be. 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. 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.
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. So your question is about deserialization rather than serialization (which is the word you're using), if I understand correctly..?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With