一尘不染

使用动态是否被认为是不好的做法?

c#

在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使用内部反射实例化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);

阅读 173

收藏
2020-05-19

共1个答案

一尘不染

简短的回答是“是”,使用动态是一种不好的做法。

为什么?

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

一般来说,类型检查有助于获得确定性的计算,因此,如果可以的话,应该使用它。这是关于
dynamic的缺点的
问题。

但是,动态可能会有用…

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

代码库在整个应用程序生命周期中都在不断发展,即使现在看起来可以动态使用,它也树立了先例,这可能意味着您的团队对动态关键字的使用有所增加。这可能会导致维护成本增加(如果上述签名有所演变,您可能会发现为时已晚)。当然,您可以依赖于单元测试,非回归人工测试等。但是,当您必须在与学科相关的质量和由计算机相关的质量自动检查之间进行选择时,请选择后者。它不太容易出错。

就你而言…

在您的情况下,您似乎可以使用通用继承方案(下面的第一个和您在问题中提到的方案),因为它 dynamic
不会给您带来任何额外的好处
(这只会使您花费更多的处理能力并使您承担责任)未来潜在错误的风险)。

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

让我们列举一下动态的各种可能替代方案…

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

最常见的是使用 接口虚拟调用 (例如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层次结构中有多个“访问者”,则可以使用 Visitor模式

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
找到

*获得一个数量级(用于一千万次调用) (。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
”),只是声明它更干净,因为它使用的代码更少,但这并没有使IMHO受益匪浅。如果您想编写时髦的代码或不关心类型安全性和生产稳定性,那么已经有很多语言可以提供全功能动态类型化。我认为dynamicC#中的关键字引入是一种弥补强类型语言家族与强类型其他语言之间差距的方法。这并不意味着您应该更改开发方式并将类型检查放到垃圾箱中。

更新: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
2020-05-19