动态的使用被认为是一种不好的做法吗?

在C#中,有人可以这样做:

MyClass myInstance = new MyClass(); dynamic mydynamicInstance = myInstance; 

然后,调用一个方法,如:

 //This method takes a MyClass argument and does something. Caller.InvokeMethod(myDynamicInstance); 

现在,这将导致在运行时确定myInstance类型,如果它有效,将正常调用Caller.InvokeMethod

现在,我的问题是,如果这被认为是使用dynamic的不良做法,尤其是在以下情况中:

1) InvokeMethod使用内部reflection实例化myDynamicInstance类型的另一个实例。

2)有一个抽象基类MyBaseClass及其子类,包括MyBaseClass 。 如果我们为所有这些派生类都有许多重载的InvokeMethod方法,我们是否可以使用它来在运行时允许类型确定,然后通过方法重载进行适当的调用(或者对方法调用的后期绑定)类)?:

 public abstract class MyBaseClass {/*...*/} public class MyClass : MyBaseClass {/*...*/} public class MyAnotherClass : MyBaseClass {/*...*/} MyBaseClass myBaseClassRef = new MyClass(); dynamic myDynamicInstance = myBaseClassRef; Caller.InvokeMethod(myDynamicInstance); 

简短的答案是肯定的,使用动态是一种不好的做法。

为什么?

dynamic关键字是指类型后期绑定,这意味着系统将仅在执行期间而不是在编译期间检查类型。 这将意味着用户而不是程序员留下来发现潜在的错误 。 错误可能是MissingMethodException,但它也可能是对具有错误行为的现有方法的无意调用。 想象一下对一种方法的调用,该方法以计算坏价或计算不良氧含量而告终。

一般来说,类型检查有助于获得确定性计算,因此,当您可以时,您应该使用它。 这是一个关于动态缺陷的问题。

但是,动态可能有用……

  • 像Office一样与COM互操作
  • 将动态类型作为语言一部分的语言 (IronPython,IronRuby)与动态语言互操作,以帮助将它们移植到.Net。
  • 可以用低典礼,优雅代码替换reflection复杂代码 (但是根据具体情况,您仍然应该分析两种方法来检查哪一种方法在性能和编译时检查方面最合适)。

代码库在整个应用程序生命周期中都在不断发展,即使动态现在看起来还不错,它也开创了一个先例,可能意味着您的团队会增加动态关键字的使用。 它可能导致维护成本增加(如果上述签名发生变化,您可能会发现它太晚了)。 当然,您可以依靠unit testing,非回归人体测试等。 但是当您必须在人类学科相关质量之间做出选择并通过计算机相关质量自动检查时,请选择以后。 它不容易出错。

在你的情况下……

在你的情况下,似乎你可以使用公共inheritance方案(下面的第一个和你在问题中提到的方案),因为dynamic不会给你任何额外的好处 (它只会花费你更多的处理能力,让你招致未来潜在错误的风险)。

这取决于您是否可以更改MyClass层次结构和/或Caller.InvokeMethod

让我们列举动态的不同替代方案……

  • 编译类型检查的动态关键字方法调用的替代方法:

最常见的是使用接口虚拟调用 ,如此instance.InvokeMethod(),inheritance调用正确的实现。

 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 instance) where T : IInvoker { return instance.InvokeMethod(); } 
  • 动态替代动态关键字方法调用:

如果您需要调用编译时未知的方法,我在下面添加了您可以使用的不同技术并更新了性能结果:

MethodInfo.CreateDelegate

  _method = typeof (T).GetMethod("InvokeMethod"); _func = (Func)_method.CreateDelegate(typeof(Func)); 

注意:需要使用Cast to 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) _dynamicMethod.CreateDelegate(typeof (Func)); 

Linq表达

它与DynamicMethod类似,但是您无法控制生成的IL。 虽然,它真的更具可读性。

  _method = typeof (T).GetMethod("InvokeMethod"); var instanceParameter = Expression.Parameter(typeof (T), "instance"); var call = Expression.Call(instanceParameter, _method); _delegate = Expression.Lambda>(call, instanceParameter).Compile(); _func = (Func) _delegate; 

MethodInfo.Invoke

最后但并非最不重要的,标准的已知reflection调用。 然而,即使它很容易弄乱它,也不要使用它,因为它真的是一个糟糕的表演者(看看基准测试结果)。 喜欢CreateDelegate,它真的更快。

  _method = typeof (T).GetMethod("InvokeMethod"); return (int)_method.Invoke(instance, _emptyParameters); 

基准测试代码可以在GitHub上找到 。

获得一个数量级的不同方法的基准 (对于10百万次调用) (.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倍 。 它可以被某些应用程序接受,因为它可以删除繁琐的代码。 它总是取决于你选择。
请记住所有的缺点。


编辑:(作为多个派遣福利的答案)

使用时尚模式名称,如“ 多次发送 ”,只是声明它更干净,因为它使用更少的代码,不会使它成为一个额外的好处恕我直言。 如果你想编写时髦的代码或者不关心类型安全性和生产稳定性,那么已经有很多语言提供全function动态类型。 我在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 

我不完全同意Fabien的说法,它不会给你带来额外的好处。 他使用访问者模式解决的问题称为多个调度 ,动态可以为此提供一个干净的解决方案。 当然你必须知道Fabien提到的性能,静态类型检查……

 public abstract class MyBaseClass { } public class MyClass : MyBaseClass { } public class MyAnotherClass : MyBaseClass { } public class ClassThatIsUsingBaseClass { public static void PrintName(MyBaseClass baseClass) { Console.WriteLine("MyBaseClass"); } public static void PrintName(MyClass baseClass) { Console.WriteLine("MyClass"); } public static void PrintName(MyAnotherClass baseClass) { Console.WriteLine("MyAnotherClass"); } public static void PrintNameMultiDispatch(MyBaseClass baseClass) { ClassThatIsUsingBaseClass.PrintName((dynamic)baseClass); } } 

用法是

 static void Main(string[] args) { MyBaseClass myClass = new MyClass(); MyBaseClass myAnotherClass = new MyAnotherClass(); ClassThatIsUsingBaseClass.PrintName(myClass); ClassThatIsUsingBaseClass.PrintName(myAnotherClass); ClassThatIsUsingBaseClass.PrintNameMultiDispatch(myClass); ClassThatIsUsingBaseClass.PrintNameMultiDispatch(myAnotherClass); Console.ReadLine(); } 

输出是

 MyBaseClass MyBaseClass MyClass MyAnotherClass 

搜索“多个调度”和“C#多个调度”以获取更多信息。