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

Nic*_*kis 40 c# reflection dynamic

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

MyClass myInstance        = new MyClass();
dynamic mydynamicInstance = myInstance;
Run Code Online (Sandbox Code Playgroud)

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

//This method takes a MyClass argument and does something.
Caller.InvokeMethod(myDynamicInstance);
Run Code Online (Sandbox Code Playgroud)

现在,这将导致在运行时确定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);
Run Code Online (Sandbox Code Playgroud)

Fab*_*Fab 61

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

为什么?

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

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

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

  • 像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 */ } }
Run Code Online (Sandbox Code Playgroud)

另一个性能稍差的是使用扩展方法

public static class InvokerEx:
{
    public static void Invoke(this MyAnotherClass c) { /* Do something */ } }
    public static void Invoke(this MyClass c) { /* Do something */ } }
}
Run Code Online (Sandbox Code Playgroud)

如果有几个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); } }
Run Code Online (Sandbox Code Playgroud)

其他变体虽然在这里不是很有用(通用方法)但对性能比较很有趣:

public void InvokeMethod<T>(T instance) where T : IInvoker { return instance.InvokeMethod(); }
Run Code Online (Sandbox Code Playgroud)
  • 动态替代动态关键字方法调用:

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

MethodInfo.CreateDelegate

        _method = typeof (T).GetMethod("InvokeMethod");
        _func = (Func<T, int>)_method.CreateDelegate(typeof(Func<T, int>));
Run Code Online (Sandbox Code Playgroud)

注意:需要使用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<T, int>) _dynamicMethod.CreateDelegate(typeof (Func<T, int>));
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

MethodInfo.Invoke

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

        _method = typeof (T).GetMethod("InvokeMethod");
        return (int)_method.Invoke(instance, _emptyParameters);
Run Code Online (Sandbox Code Playgroud)

可以在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
Run Code Online (Sandbox Code Playgroud)

编辑:

因此,与访客模式相比,动态调度只差3倍.对于某些应用程序来说它是可以接受的,因为它可以删除繁琐的代码.它总是取决于你选择.
请记住所有的缺点.


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

使用像' multi dispatch ' 这样的流行模式名称,只是说它更干净,因为它使用更少的代码,并没有使它成为一个额外的好处恕我直言.如果您想编写时髦的代码或者不关心类型安全性和生产稳定性,那么已经有很多语言提供全功能动态类型.我dynamic在C#中看到了关键字介绍,以此来缩小强类型语言系列与不那么强类型的其他语言之间的差距.这并不意味着您应该改变开发方式并将类型检查放入垃圾箱.

更新: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
Run Code Online (Sandbox Code Playgroud)

  • 接受动态时的另一个场景:在编译时从源未知接收数据,例如当cols仅在运行时知道时,SQL行(或JSON).替代实现是Dictionary <string,Object>.两者都对"未知专栏"很敏感.完全同意,出于少数情况,这是不好的做法 (4认同)

Mur*_*fal 5

我不完全同意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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法是

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();
}
Run Code Online (Sandbox Code Playgroud)

输出是

MyBaseClass
MyBaseClass
MyClass
MyAnotherClass
Run Code Online (Sandbox Code Playgroud)

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


Joh*_*ers 5

这个问题在 2015 年得到了回答,今天在 2019 年,我们在 JavaScript 和 Typescript 中看到的模式确实有充分的理由使用动态;但是,它需要开发人员谨慎行事。

    private (Boolean Valid, dynamic Result) ValidateID(int ID)
    {
        var person = _store.Persons.FirstOrDefault(person => person.ID == ID);
        if (person == null)
        {
            string message = $"The person id {ID} does not exist, please try again.";
            return (false, message);
        }
        return (true, person);

    }
Run Code Online (Sandbox Code Playgroud)

要使用上面的代码:

   var operation = ValidatePersonID(personID);
   if (operation.Valid == false)
     {
        //BadRequest takes a string and C# uses co-variance to make it work.
        return BadRequest(operation.Result);
     }
   //otherwise just cast the type, but requires the cast to always work.
   var pe = (Person)operation.Result;
   ...
Run Code Online (Sandbox Code Playgroud)

有效性的返回值是 True 还是 False 决定了返回的类型。我们仍然在 BadRequest 上使用强制类型转换和/或所需的输入类型来选择强类型。我们还选择了编译时类型检查,如果两个返回类型之一更改为字符串和/或人员以外的其他内容,它将无法编译。

我认为这是依赖注入的一种形式,我们根据注入的内容实现不同的行为。C# 纯粹主义者不喜欢这个想法,但 Typescript 的人一直这样做。