C#中的动态:用法配方

这是动态语言运行时系列的最后一部分。 以前的文章:

  1. 动态细节:编译器卧底游戏,内存泄漏,性能差异 。 本文详细讨论了DLR缓存以及对开发人员重要的要点。
  2. Grokl DLR 。 技术概述,DynamicMetaObject的剖析以及有关如何创建自己的动态类的简短说明。

在这篇简短的文章中,我们最终将分析在现实生活中使用动态的主要情况:什么时候没有它,什么时候可以使生活更轻松。



当动态必不可少


没有这种情况。 您可以始终以静态样式编写功能相似的代码,唯一的区别是易于阅读和代码量大。 例如,当使用COM对象而不是动态对象时,可以使用反射。

动态时有用


使用COM对象


首先,当然,这是与COM对象一起工作的,因此所有这些都已开始。 将获得的代码与动态和反射进行比较:

dynamic instance = Activator.CreateInstance(type); instance.Run("Notepad.exe"); 

 var instance = Activator.CreateInstance(type); type.InvokeMember("Run", BindingFlags.InvokeMethod, null, instance, new[] { "Notepad.exe" }); 

通常,要通过反射使用COM对象,必须为每个方法/属性创建带有包装的分支类。 还有一些不太明显的优点,例如,通过dynamic调用方法时,不需要填写不需要的参数(从COM对象的角度来看是强制性的)的能力。

使用配置


另一个教科书示例正在使用XML等配置。 没有动态

 XElement person = XElement.Parse(xml); Console.WriteLine( $"{person.Descendants("FirstName").FirstOrDefault().Value} {person.Descendants("LastName").FirstOrDefault().Value}" ); 

具有动态:

 dynamic person = DynamicXml.Parse(xml); Console.WriteLine( $"{person.FirstName} {person.LastName}" ); 

当然,为此,您需要实现自己的动态类。 作为第一个清单的替代方法,您可以编写一个类似这样的类:

 var person = StaticXml.Parse(xml); Console.WriteLine( $"{person.GetElement("FirstName")} {person.GetElement("LastName")}" ); 

但是,您看,这看起来比通过dynamic显得优雅得多。

使用外部资源


上一段可以概括为使用外部资源进行的任何操作。 我们总是有两种选择:使用动态方式以本机C#样式获取代码,或使用“魔术线”进行静态键入。 让我们看一个带有REST API请求的示例。 使用dynamic,您可以这样编写:

 dynamic dynamicRestApiClient = new DynamicRestApiClient("http://localhost:18457/api"); dynamic catsList = dynamicRestApiClient.CatsList; 

我们的动态类将根据属性的请求发送表单请求

 [GET] http://localhost:18457/api/catslist 

然后,他将其反序列化并返回给我们一系列已经准备好用于预期用途的猫。 没有动态,它将看起来像这样:

 var restApiClient = new RestApiClient("http://localhost:18457/api"); var catsListJson = restApiClient.Get("catsList"); var deserializedCatsList = JsonConvert.DeserializeObject<Cat[]>(catsListJson); 

反射更换


在前面的示例中,您可能会遇到一个问题:为什么在一种情况下我们将返回值反序列化为特定类型,而在另一种情况下却没有呢? 事实是,在静态类型化中,我们需要将对象显式转换为Cat类型才能使用它们。 在dynamic的情况下,将JSON反序列化为我们的动态类中的对象数组并从中返回object []就足够了,因为dynamic负责反射。 我将给出两个示例说明其工作原理:

 dynamic deserialized = JsonConvert.DeserializeObject<object>(serialized); var name = deserialized.Name; var lastName = deserialized.LastName; 

 Attribute[] attributes = type.GetCustomAttributes(false).OfType<Attribute>(); dynamic attribute = attributes.Single(x => x.GetType().Name == "DescriptionAttribute"); var description = attribute.Description; 

与使用COM对象时的原理相同。

参观者


使用动态,您可以非常优雅地实现此模式。 而不是一千个字:

 public static void DoSomeWork(Item item) { InternalDoSomeWork((dynamic) item); } private static void InternalDoSomeWork(Item item) { throw new Exception("Couldn't find handler for " + item.GetType()); } private static void InternalDoSomeWork(Sword item) { //do some work with sword } private static void InternalDoSomeWork(Shield item) { //do some work with shield } public class Item { } public class Sword : Item {} public class Shield : Item {} 

现在,将Sword类型的对象传递给DoSomeWork方法时,将调用InternalDoSomeWork(Sword项目)方法。

结论


使用动态的优点:

  • 可用于快速原型制作:在大多数情况下,样板代码的数量减少
  • 通常,它可以提高代码的可读性和美观性(由于从“魔术线”过渡到语言的本机样式)
  • 尽管有广泛的意见,但由于有了缓存机制,一般情况下并不会产生显着的性能开销

使用动态的缺点:

  • 内存和性能之间存在明显的细微差别。
  • 在此类动态类的支持和阅读下,您需要很好地了解正在发生的事情
  • 程序员被剥夺了类型检查和编译器提供的所有运行状况保证

结论


我认为,在以下情况下,使用动态开发人员将获得最大的收益:

  • 原型制作时
  • 在小型/家庭项目中,错误成本低
  • 在小的代码大小的实用程序中,并不意味着运行时间长。 如果您的实用程序在最坏的情况下执行了几秒钟,则通常无需考虑内存泄漏和性能下降

至少有争议的是在具有大量代码库的复杂项目中使用动态 -在这里最好花一些时间编写静态包装,从而将不明显的时刻减少到最少。

如果您使用的COM对象或服务/产品中的域意味着连续的长时间工作,则最好不要使用dynamic ,尽管它是为此类情况而创建的。 即使您完全知道怎么做和怎么做,也不会犯错,但迟早会有一个不知道这一点的新开发人员来临。 结果很可能是难以计算的内存泄漏。

Source: https://habr.com/ru/post/zh-CN470355/


All Articles