在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);
答案 0 :(得分:54)
<强>为什么吗
动态关键字是指类型后期绑定,这意味着系统将仅在执行期间检查类型而不是 在编译期间。这将意味着用户,而不是程序员,留下来发现潜在的错误。错误可能是 一个MissingMethodException,但它也可能是对不良行为的现有方法的无意调用。 想象一下对一种方法的调用,该方法以计算坏价或计算不良氧含量而告终。
一般来说,类型检查有助于获得确定性计算,因此,当您可以时,您应该使用它。这是关于shortcomings of dynamic的问题。
代码库在整个应用程序生命周期中都在不断发展,即使动态现在看来还可以, 它开创了一个先例,可能意味着您的团队增加了动态关键字的使用。它可以导致增加 维护成本(如果上述签名发展,你可能会发现它太晚了)。当然,你可以 依靠单元测试,非回归人体测试等。但是当你必须在人类学科相关之间做出选择时 质量和计算机相关质量自动检查,选择以后。它不容易出错。
在你的情况下,似乎你可以使用公共继承方案(下面的第一个和你在问题中提到的那个),
因为 dynamic
无法为您提供任何额外的好处(它只会让您获得更多处理能力并让您产生费用
未来潜在错误的风险)。
这取决于您是否可以更改MyClass
层次结构和/或Caller.InvokeMethod
的代码。
最常见的是使用接口虚拟调用,例如此实例.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 */ } }
}
如果有几个访客&#34;在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>));
注意:需要使用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>));
Linq Expression
它与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上找到基准测试代码。
不同方法的基准获得一个数量级(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倍。对于某些应用程序来说它是可以接受的,因为它可以删除繁琐的代码。它总是取决于你选择。
请记住所有的缺点。
编辑(作为多次发送福利的答案)
使用时髦的模式名称,例如&#39; 多次发送&#39;并且只是声明它更干净,因为它使用更少的代码,并没有使它成为一个额外的好处恕我直言。
如果您想编写时髦的代码或者不关心类型安全性和生产稳定性,那么已经有很多语言提供全功能动态类型。我在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
答案 1 :(得分:4)
我不完全同意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#多次发送”。
答案 2 :(得分:1)
我们在JavaScript和Typescript中看到的模式在2015年,今天的2019年得到了回答,确实有充分的理由使用动态。但是,这需要开发人员注意。
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);
}
要使用上面的代码:
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;
...
返回的有效性为True或False确定要返回的类型。我们仍然使用强制转换和BadRequest上的必需输入类型来选择强类型。我们还选择了编译时类型检查,如果返回的两种类型之一更改为字符串和/或person以外的其他值,则不会编译。
我认为这是依赖注入的一种形式,根据注入的不同,我们可以实现不同的行为。 C#纯粹主义者不喜欢这个主意,但是Typescript的人一直都喜欢。