简短的回答是肯定的,使用动态是一种不好的做法。
为什么?
dynamic 关键字是指类型后期绑定,这意味着系统只会在执行过程中检查类型,而不是
在编译期间。这将意味着用户,而不是程序员,要发现潜在的错误。错误可能是
MissingMethodException,但它也可能是对具有不良行为的现有方法的无意调用。
想象一下调用一个以计算错误价格或计算错误氧气水平而告终的方法。
一般来说,类型检查有助于获得确定性计算,因此,您应该尽可能使用它。这是shortcomings of dynamic的问题。
但是,动态可能很有用...
代码库在整个应用程序生命周期中不断发展,即使现在动态看起来还不错,
它开创了一个先例,这可能意味着您的团队会增加动态关键字的使用。它可能导致增加
维护成本(如果上述签名发生变化,您可能会为时已晚)。当然,你可以
依靠单元测试、非回归人类测试等等。但是当你必须在与人类学科相关的
质量并由计算机自动检查相关质量,选择后者。它更不容易出错。
在你的情况下......
在您的情况下,您似乎可以使用通用继承方案(下面的第一个和您在问题中提到的那个),
因为 dynamic 不会给您带来任何额外的好处(它只会消耗您更多的处理能力并让您承担
未来潜在错误的风险)。
这取决于您是否可以更改MyClass 层次结构和/或Caller.InvokeMethod 的代码。
让我们列举
动态的不同可能替代方案...
最常见的是使用 interface virtual call 像这样的 instance.InvokeMethod() 继承调用正确的实现。
public interface IInvoker : { void InvokeMethod(); }
public abstract class MyBaseClass : IInvoker { public abstract void InvokeMethod(); }
public class MyAnotherClass : MyBaseClass { public override void InvokeMethod() { /* Do something */ } }
public class MyClass : MyBaseClass { public override void InvokeMethod() { /* Do something */ } }
另一个性能稍差的方法是使用扩展方法
public static class InvokerEx:
{
public static void Invoke(this MyAnotherClass c) { /* Do something */ } }
public static void Invoke(this MyClass c) { /* Do something */ } }
}
如果 MyBaseClass 层次结构中有多个“访问者”,则可以使用 访问者模式:
public interface IVisitor
{
void Visit(this MyAnotherClass c);
void Visit(this MyClass c);
}
public abstract class MyBaseClass : IInvoker { public abstract void Accept(IVisitor visitor); }
public class MyAnotherClass : MyBaseClass { public override void Accept(IVisitor visitor) { visitor.Visit(this); } }
public class MyClass : MyBaseClass { public override void Accept(IVisitor visitor) { visitor.Visit(this); } }
其他变体虽然在这里不是很有用(通用方法)但对性能比较很有趣:
public void InvokeMethod<T>(T instance) where T : IInvoker { return instance.InvokeMethod(); }
如果您需要在编译时调用未知的方法,我在下面添加了您可以使用的不同技术并更新了性能结果:
MethodInfo.CreateDelegate
_method = typeof (T).GetMethod("InvokeMethod");
_func = (Func<T, int>)_method.CreateDelegate(typeof(Func<T, int>));
注意:需要强制转换为 Func 以避免调用 DynamicInvoke(因为它通常较慢)。
DynamicMethod 和 ILGenerator.Emit
它实际上是从头开始构建完整的调用,它是最灵活的,但您必须有一些汇编程序背景才能完全理解它。
_dynamicMethod = new DynamicMethod("InvokeMethod", typeof (int), new []{typeof(T)}, GetType().Module);
ILGenerator il = _dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, _method);
il.Emit(OpCodes.Ret);
_func = (Func<T, int>) _dynamicMethod.CreateDelegate(typeof (Func<T, int>));
Linq 表达式
它类似于 DynamicMethod,但是您不控制生成的 IL。不过,它确实更具可读性。
_method = typeof (T).GetMethod("InvokeMethod");
var instanceParameter = Expression.Parameter(typeof (T), "instance");
var call = Expression.Call(instanceParameter, _method);
_delegate = Expression.Lambda<Func<T, int>>(call, instanceParameter).Compile();
_func = (Func<T, int>) _delegate;
MethodInfo.Invoke
最后但并非最不重要的标准已知反射调用。
但是,即使它很容易弄乱,也不要使用它,因为它的性能确实很差(查看基准测试结果)。更喜欢 CreateDelegate,它真的更快。
_method = typeof (T).GetMethod("InvokeMethod");
return (int)_method.Invoke(instance, _emptyParameters);
基准测试的代码可以在GitHub找到。
不同方法的基准测试以获得数量级(对于 1000 万次调用)(.NET Framework 4.5):
For Class standard call:
Elapsed: 00:00:00.0532945
Call/ms: 188679
For MethodInfo.CreateDelegate call:
Elapsed: 00:00:00.1131495
Call/ms: 88495
For Keyword dynamic call:
Elapsed: 00:00:00.3805229
Call/ms: 26315
For DynamicMethod.Emit call:
Elapsed: 00:00:00.1152792
Call/ms: 86956
For Linq Expression call:
Elapsed: 00:00:00.3158967
Call/ms: 31746
For Extension Method call:
Elapsed: 00:00:00.0637817
Call/ms: 158730
For Generic Method call:
Elapsed: 00:00:00.0772658
Call/ms: 129870
For Interface virtual call:
Elapsed: 00:00:00.0778103
Call/ms: 129870
For MethodInfo Invoke call:
Elapsed: 00:00:05.3104416
Call/ms: 1883
For Visitor Accept/Visit call:
Elapsed: 00:00:00.1384779
Call/ms: 72463
== SUMMARY ==
Class standard call: 1
Extension Method call : 1,19
Generic Method call : 1,45
Interface virtual call : 1,45
MethodInfo.CreateDelegate call : 2,13
DynamicMethod.Emit call : 2,17
Visitor Accept/Visit call : 2,60
Linq Expression call : 5,94
Keyword dynamic call : 7,17
MethodInfo Invoke call : 100,19
编辑:
因此,与访问者模式相比,动态调度仅慢 3 倍。某些应用程序可以接受它,因为它可以删除繁琐的代码。选择始终由您决定。
请记住所有缺点。
编辑:(作为对多次调度好处的回答)
使用像'multiple dispatch'这样的流行模式名称,只是说它更干净,因为它使用更少的代码,恕我直言,它并没有带来额外的好处。
如果您想编写时髦的代码或不关心类型安全和生产稳定性,那么已经有很多语言提供全功能动态类型。我认为 C# 中的 dynamic 关键字介绍是一种缩小强类型语言家族与其他非强类型语言之间差距的方法。这并不意味着您应该改变开发方式并将类型检查丢弃。
更新:2016/11/08 (.NET Framework 4.6.1)
数量级保持不变(即使其中一些有所改进):
Class standard call: 1
Extension Method call : 1,19
Interface virtual call : 1,46
Generic Method call : 1,54
DynamicMethod.Emit call : 2,07
MethodInfo.CreateDelegate call : 2,13
Visitor Accept/Visit call : 2,64
Linq Expression call : 5,55
Keyword dynamic call : 6,70
MethodInfo Invoke call : 102,96