c# - 使用动态是否被认为是一种不好的做法?

标签 c# dynamic reflection

在 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对于所有这些派生类,我们是否可以使用它来允许在运行时确定类型,然后通过方法重载(或对该类方法的调用进行后期绑定(bind))进行正确调用?:
public abstract class MyBaseClass         {/*...*/}
public class MyClass        : MyBaseClass {/*...*/}
public class MyAnotherClass : MyBaseClass {/*...*/}

MyBaseClass myBaseClassRef = new MyClass();
dynamic myDynamicInstance  = myBaseClassRef;

Caller.InvokeMethod(myDynamicInstance);

最佳答案

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

为什么?

动态关键字指的是类型后期绑定(bind),这意味着系统只会在执行期间检查类型而不是
在编译过程中。那么这将意味着 用户 ,而不是程序员,留下来发现潜在的错误 .错误可能是
MissingMethodException,但它也可能是对具有不良行为的现有方法的无意调用。
想象一下,调用一个以计算错误价格或计算错误氧气水平结束的方法。

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

但是,动态可能很有用...

  • Interop with COM like with Office
  • 与动态类型是语言一部分的语言互操作 (IronPython, IronRuby) as dynamic 被引入以帮助将它们移植到 .Net。
  • 可以用低仪式、优雅的代码代替反射复杂的代码 (但是,根据具体情况,您仍然应该分析这两种方法,以检查哪一种在性能和编译时检查方面最合适)。

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

    在你的情况...

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

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

    我们来一一列举
    动态的不同可能替代方案...
  • 动态关键字方法调用的编译类型检查替代方法:

  • 最常见的是使用 接口(interface)虚拟调用像这个 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 倍 .对于某些应用程序来说是可以接受的,因为它可以删除繁琐的代码。始终由您来选择。只要记住所有的缺点。

    编辑: (作为对多重 dispatch 利益的回答)

    使用像“multiple dispatch”这样的流行模式名称,并只是说它更干净,因为它使用的代码更少,恕我直言,这并没有使它成为一个额外的好处。
    如果你想编写流行的代码或者不关心类型安全和生产稳定性,已经有很多语言提供全功能动态类型。我看到dynamic C# 中的关键字引入是一种缩小强类型语言家族与其他非强类型语言之间差距的方法。这并不意味着您应该改变开发方式并将类型检查丢弃。

    更新:2016/11/08 (.NET 框架 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
    

    关于c# - 使用动态是否被认为是一种不好的做法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31859016/

    相关文章:

    c# - 使用 XML 文件绑定(bind)下拉列表

    ruby-on-rails - 如何在rails函数中创建动态变量?

    java - Integer 类型引用 String 类型值

    c# - 另一个对象中的对象名称

    javascript - 动态 (AJAX) HTML 创建

    java - "Argument is not an array"使用Java反射设置数组值时出现异常

    c# - 为什么 graphql-dotnet 会为此模式返回 "Expected non-null value"错误?

    c# - C# 3 中具有接口(interface)继承(co(ntra)-variance?)的泛型类型推断

    c# - 将函数实现为 []

    css - 为什么样式不根据div大小变化呢?