我正在使用我无法改变的协议为interop编写一个自定义序列化程序。我正在使用反射来提取结构成员值并将它们写入BinaryWriter
。它仅用于支持基本类型和数组。
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));
显然这很难看,当我想对这些类型的数组做同样的事情时,它变得更加难看。
如果我可以做这样的事情,那真的很好:
bw.Write( (fi.FieldType) fi.GetValue(obj) );
然后为数组做类似的事情。
有什么想法吗?
答案 0 :(得分:4)
您可以使用反射来调用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) });
}
答案 1 :(得分:2)
这段代码根本不是很难看......它只是重复性的。但它实际上非常干净,简短且易于理解。如果您有一百万种不同的类型,那将是一回事,但数量有限。
如果你能够做你想做的事情,如果它有任何问题或者它需要做更多的事情并且其他程序员可能不理解它将很难维护......或者你可能忘了你做了什么,不得不重新学习它。
通过这样做,您将拥有: 增加了额外的开发时间 减少了可读性 - 降低速度 - 增加维护
有时我们喜欢过于简单的问题并使它们更具挑战性。但通常,良好的商业代码只是平凡无聊的代码。
答案 2 :(得分:2)
如果要简化它,可以使用表达式动态地进行正确的调用。
//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));
我们在这里做的是动态生成代码以进行二进制编写器所需的调用。这听起来比它复杂,但我们正在做的是为BinaryWriter
的“Write”方法创建一个表达式。我们还使用Expression.Convert
动态转换它,以便调用Write
的正确重载。我们接受BinaryWriter的两个参数和要写的值。最后,我们编译lambda并为该类型缓存它以便以后重用。
根据您的需要,这比使用BinaryWriter
上的反射要快得多。
答案 3 :(得分:2)
我为protobuf-net做了一些非常相似的代码; Type.GetTypeCode(...)
是一个福音,允许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
}
仍然有点重复,但您只需查看Type
一次 - 其余为switch
。
如果你使用4.0,另一个技巧可能是:
dynamic value = fi.GetValue(obj);
bw.Write(value);
将尝试在运行时选择最合适的重载。但是,在我看来,这不足以在此处使用dynamic
。
最后的想法是:使用元编程(例如ILGenerator
)在运行时创建代码 - 更复杂但更快,并且在执行时没有任何这些检查< / em>时间(就在准备模型时)。
答案 4 :(得分:1)
我可以想到三个选择:
1)BinaryFormatter
- 这可以通过Serialize
方法非常简单地完成您的任务。
2)如你所知,使用反射。代码看起来像这样:
// 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)使用T4模板快速生成代码。代码仍然很难看,但至少需要维护很少的工作。我经常在我的一些项目中使用这种模式,因为它是两个世界中最好的 - 反射没有性能损失,但动态生成代码的所有好处。
答案 5 :(得分:0)
即使你没有做任何其他事情,switch
也可以使用字符串,它可以让你更容易阅读。
鉴于显式演员正在发挥作用:
Type t = Type.GetType(String.Concat("System.", fi.FieldType.Name));
然后使用
MethodInfo m = typeof(BinaryWriter).GetMethod("Write", new type[] { t });
如果它不是空的
m.Invoke(bw, new object[] { fi.GetValue(obj) });
这假设FieldType.Name
对应于范围内的类型。没有说明数组中会有什么,但是如果它是Int16[]
,它只是一些jiggery pokery,可能是子类化BinaryWriter
并为类型中的类型添加更多重载盒子不处理。
如果您正在做很多这样的事情,某种缓存Name
,Type
和MethodInfo
可能会有用。