Dynamic casting of unknown types for serialization

I am writing a special serializer for structure types to interact with a protocol that I cannot change. I use reflection to pull out the values ​​of structure elements and write them to BinaryWriter . It is intended only to support basic types and arrays.

 if (fi.FieldType.Name == "Int16") bw.Write((Int16)fi.GetValue(obj)); else if (fi.FieldType.Name == "UInt16") bw.Write((UInt16)fi.GetValue(obj)); else if (fi.FieldType.Name == "Int32") bw.Write((Int32)fi.GetValue(obj)); else if (fi.FieldType.Name == "UInt32") bw.Write((UInt32)fi.GetValue(obj)); else if (fi.FieldType.Name == "Int64") bw.Write((Int64)fi.GetValue(obj)); else if (fi.FieldType.Name == "UInt64") bw.Write((UInt64)fi.GetValue(obj)); else if (fi.FieldType.Name == "Single") bw.Write((float)fi.GetValue(obj)); else if (fi.FieldType.Name == "Double") bw.Write((double)fi.GetValue(obj)); else if (fi.FieldType.Name == "Decimal") bw.Write((decimal)fi.GetValue(obj)); else if (fi.FieldType.Name == "Byte") bw.Write((byte)fi.GetValue(obj)); else if (fi.FieldType.Name == "SByte") bw.Write((sbyte)fi.GetValue(obj)); else if (fi.FieldType.Name == "String") bw.Write((string)fi.GetValue(obj)); 

Obviously, this is ugly, and it becomes even more ugly when I want to do the same with arrays of these types too.

Which would be very nice if I could do something like this:

 bw.Write( (fi.FieldType) fi.GetValue(obj) ); 

Then do the same for arrays.

Any ideas?

+6
source share
6 answers

You can use reflection to invoke the correct version of Write

 public static void WriteField(BinaryWriter bw, object obj, FieldInfo fieldInfo) { typeof(BinaryWriter) .GetMethod("Write", new Type[] { fieldInfo.FieldType }) .Invoke(bw, new object[] { fieldInfo.GetValue(obj) }); } 
+4
source

This code is not entirely ugly ... it just repeats itself. But this is actually quite clean, short and very easy to understand. If you had a million different types to take this into account, it would be one, but there is only a limited number.

If you are able to do what you want, it will be difficult to support if he ever has a problem, or he needs to do something, and another programmer may not understand this ... or you may forget what you did, and have to relearn it.

By doing this, you will receive: additional additional development time - provided readability speed - improved service

Sometimes we like problems that are too simple and make them more complicated. But often a good business code is just a mundane, boring code.

+2
source

If you want to simplify it, you can use an expression to dynamically call correctly.

 //Cache the generated method for re-use later, say as a static field of dictionary. It shouldn't grow too-big given the number of overloads of Write. private static Dictionary<Type, Action<BinaryWriter, object>> _lambdaCache = new Dictionary<Type, Action<BinaryWriter, object>>(); //... if (!_lambdaCache.ContainsKey(fi.FieldType)) { var binaryWriterParameter = Expression.Parameter(typeof(BinaryWriter)); var valueParameter = Expression.Parameter(typeof(object)); var call = Expression.Call(binaryWriterParameter, "Write", null, Expression.Convert(valueParameter, fi.FieldType)); var lambda = Expression.Lambda<Action<BinaryWriter, object>>(call, binaryWriterParameter, valueParameter).Compile(); _lambdaCache.Add(fi.FieldType, lambda); } var write = _lambdaCache[fi.FieldType]; write(bw, fi.GetValue(obj)); 

What we are doing here is dynamically generating code to make the call you need for binary writing. This sounds a lot more complicated than what we do, but we create an expression for the "Write" method for BinaryWriter . We also dynamically apply it using Expression.Convert , so the correct Write overload is called. We take two BinaryWriter parameters and a value for writing. Finally, we compile lambda and cache for this type for reuse later.

Depending on your needs, this will be much faster than using reflection over BinaryWriter .

+2
source

I make very similar code for protobuf-net; Type.GetTypeCode(...) is a boon allowing switch :

 switch(Type.GetTypeCode(fi.FieldType)) { case TypeCode.Int16: bw.Write((Int16)fi.GetValue(obj)); break case TypeCode.UInt32: bw.Write((UInt16)fi.GetValue(obj)); break; ... etc lots and lots } 

still repeating a little, but you only look at Type once, the rest is switch .

If you are using 4.0, another trick might be:

 dynamic value = fi.GetValue(obj); bw.Write(value); 

who will try to choose the most appropriate overload at runtime. However, in my opinion , this is not the reason - just use dynamic here.

The last thought was this: use meta-programming (for example, ILGenerator ) to create code at runtime - more complex, but faster and does not have any of these checks at runtime (just when preparing the model).

+2
source

I can imagine three options:

1) BinaryFormatter - this can make your task very simple, using the Serialize method.
2) As you suggest, using reflection. The code will look something like this:

 // sample source data object src = (uint)234; var bwType = typeof(BinaryWriter); var argTypes = new Type[] { src.GetType() }; var m = bwType.GetMethod("Write", argTypes); var args = new object[] { src }; m.Invoke(bw, args); 

3) Use the T4 template to quickly create code. The code is still ugly, but at least it requires much less work to maintain. I often use this template in some of my projects, because it is the best of both worlds - the lack of response from reflection, but all the advantages of dynamically generated code.

+1
source

Even if you are not doing anything, switch works with strings, and this will make it much easier for you to read.

Given that explicit casting works:

 Type t = Type.GetType(String.Concat("System.", fi.FieldType.Name)); 

Then use

 MethodInfo m = typeof(BinaryWriter).GetMethod("Write", new type[] { t }); 

If it is not null

 m.Invoke(bw, new object[] { fi.GetValue(obj) }); 

This assumes that FieldType.Name matches the type that is in scope. I didn’t say what would be there for the array, but if it is Int16[] , this is just a little pogery jiggery and can be a subclass of BinaryWriter and add some extra overloads for types that are not used in the box to deal with. If you do this a lot, perhaps something like a cache of Name , Type and MethodInfo .

0
source

Source: https://habr.com/ru/post/901470/


All Articles