简体   繁体   English

.NET 4.0中的动态:我做得对吗?

[英]Dynamics in .NET 4.0: am I doing it right?

Yesterday I wrote my first lines of code using the new dynamic type in .NET 4.0. 昨天我使用.NET 4.0中的新dynamic类型编写了我的第一行代码。 The scenario where I found this useful is as follows: 我发现这个有用的场景如下:

I have a class holding several lists of values. 我有一个类,里面有几个值列表。 This can be List<string> , List<bool> , List<int> or really any kind of list. 这可以是List<string>List<bool>List<int>或任何类型的列表。 The way these are used, is that I add a value to one or more of these lists. 使用它们的方式是我向这些列表中的一个或多个添加值。 Then I "synchronize" them, so that they all end up the same length (those too short are filled with a default value). 然后我“同步”它们,以便它们都以相同的长度结束(那些太短的都填充了默认值)。 And then I continue to add more values, sync again etc. The goal is that the item at any index in one of the lists are related to an item at the same index in another list. 然后我继续添加更多值,再次同步等。目标是其中一个列表中任何索引处的项与另一个列表中相同索引处的项相关。 (Yes, this could probably be better solved by wrapping all this in another class, but that's not the point in this case.) (是的,通过将所有这些包装在另一个类中可能可以更好地解决这个问题,但在这种情况下,这不是重点。)

I have this construct in a couple of classes, so I wanted to make this synchronizing of the lists as generic as possible. 我有几个类中的这个构造,所以我想让列表的同步尽可能通用。 But since the inner type of the lists might vary, this wasn't as straight forward as I first had thought. 但由于列表的内部类型可能会有所不同,这并不像我最初想象的那样直截了当。 But, enter the hero of the day: dynamics :) 但是,进入当天的英雄:动态:)

I wrote the following helper class that can take a collection of lists (of any type) together with a default value for each list: 我编写了以下帮助器类,它可以获取列表(任何类型)的集合以及每个列表的默认值:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Foo.utils
{
    public class ListCollectionHelper
    {
        /// <summary>
        /// Takes a collection of lists and synchronizes them so that all of the lists are the same length (matching
        /// the length of the longest list present in the parameter).
        /// 
        /// It is assumed that the dynamic type in the enumerable is of the type Tuple&lt;ICollection&lt;T>, T>, i.e. a
        /// list of tuples where Item1 is the list itself, and Item2 is the default value (to fill the list with). In
        /// each tuple, the type T must be the same for the list and the default value, but between the tuples the type
        /// might vary.
        /// </summary>
        /// <param name="listCollection">A collection of tuples with a List&lt;T> and a default value T</param>
        /// <returns>The length of the lists after the sync (length of the longest list before the sync)</returns>
        public static int SyncListLength(IEnumerable<dynamic> listCollection)
        {
            int maxNumberOfItems = LengthOfLongestList(listCollection);
            PadListsWithDefaultValue(listCollection, maxNumberOfItems);
            return maxNumberOfItems;
        }

        private static int LengthOfLongestList(IEnumerable<dynamic> listCollection)
        {
            return listCollection.Aggregate(0, (current, tuple) => Math.Max(current, tuple.Item1.Count));
        }

        private static void PadListsWithDefaultValue(IEnumerable<dynamic> listCollection, int maxNumberOfItems)
        {
            foreach (dynamic tuple in listCollection)
            {
                FillList(tuple.Item1, tuple.Item2, maxNumberOfItems);
            }
        }

        private static void FillList<T>(ICollection<T> list, T fillValue, int maxNumberOfItems)
        {
            int itemsToAdd = maxNumberOfItems - list.Count;

            for (int i = 0; i < itemsToAdd; i++)
            {
                list.Add(fillValue);
            }
        }
    }
}

And below is a short set of unit tests I used to verify that I ended up with the desired behaviour: 以下是我用来验证我最终得到所需行为的一小部分单元测试:

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Foo.utils;

namespace Foo.UnitTests
{
    [TestClass]
    public class DynamicListSync
    {
        private readonly List<string> stringList = new List<string>();
        private readonly List<bool> boolList = new List<bool>();
        private readonly List<string> stringListWithCustomDefault = new List<string>();
        private readonly List<int> intList = new List<int>();

        private readonly List<dynamic> listCollection = new List<dynamic>();

        private const string FOO = "bar";

        [TestInitialize]
        public void InitTest()
        {
            listCollection.Add(Tuple.Create(stringList, default(String)));
            listCollection.Add(Tuple.Create(boolList, default(Boolean)));
            listCollection.Add(Tuple.Create(stringListWithCustomDefault, FOO));
            listCollection.Add(Tuple.Create(intList, default(int)));
        }

        [TestMethod]
        public void SyncEmptyLists()
        {
            Assert.AreEqual(0, ListCollectionHelper.SyncListLength(listCollection));
        }

        [TestMethod]
        public void SyncWithOneListHavingOneItem()
        {
            stringList.Add("one");
            Assert.AreEqual(1, ListCollectionHelper.SyncListLength(listCollection));

            Assert.AreEqual("one", stringList[0]);
            Assert.AreEqual(default(Boolean), boolList[0]);
            Assert.AreEqual(FOO, stringListWithCustomDefault[0]);
            Assert.AreEqual(default(int), intList[0]);
        }

        [TestMethod]
        public void SyncWithAllListsHavingSomeItems()
        {
            stringList.Add("one");
            stringList.Add("two");
            stringList.Add("three");
            boolList.Add(false);
            boolList.Add(true);
            stringListWithCustomDefault.Add("one");

            Assert.AreEqual(3, ListCollectionHelper.SyncListLength(listCollection));

            Assert.AreEqual("one", stringList[0]);
            Assert.AreEqual("two", stringList[1]);
            Assert.AreEqual("three", stringList[2]);

            Assert.AreEqual(false, boolList[0]);
            Assert.AreEqual(true, boolList[1]);
            Assert.AreEqual(default(Boolean), boolList[2]);

            Assert.AreEqual("one", stringListWithCustomDefault[0]);
            Assert.AreEqual(FOO, stringListWithCustomDefault[1]);
            Assert.AreEqual(FOO, stringListWithCustomDefault[2]);

            Assert.AreEqual(default(int), intList[0]);
            Assert.AreEqual(default(int), intList[1]);
            Assert.AreEqual(default(int), intList[2]);
        }
    }
}

So, since this is my first shot at dynamics (both in C# and anywhere else really...), I just wanted to ask if I'm doing this right. 所以,既然这是我动态的第一次镜头(无论是在C#还是其他任何地方......),我只想问我是否正确行事。 Obviously the code works as intended, but is this the right way of doing it? 显然代码按预期工作,但这是正确的方法吗? Are there any obvious optimizations or pitfalls I'm missing etc? 我有什么明显的优化或缺陷吗?

I've done quite a bit of hacking around with dynamics in C#, I initially thought they would be really neat as I'm a big fan of the dynamic typing done by Ruby/Javascript, but was sadly dissapointed in the implementation. 我已经对C#中的动态做了很多讨论,我最初认为它们会非常整洁,因为我是Ruby / Javascript完成的动态类型的忠实粉丝,但在实现中却很遗憾。 As such, my opinion for "Am I doing it right" comes down to "is this problem a good fit for dynamics" - here are my thoughts on that. 因此,我对“我是否正确行事”的看法归结为“这个问题非常适合动态” - 这是我对此的看法。

  • the performance hit on load to pull in and JIT all the dynamic-related assemblies can be quite severe. 拉入的负载性能和JIT所有与动态相关的组件可能非常严重。
  • The C-Sharp runtime binder internally throws and catches an exception the first time a method is resolved dynamically. C-Sharp运行时绑定程序在内部抛出并在第一次动态解析方法时捕获异常。 This happens per call-site (ie if you have 10 lines of code calling methods on a dynamic object you get 10 exceptions). 每个调用站点都会发生这种情况(例如,如果您在动态对象上有10行代码调用方法,则会获得10个异常)。 This is really annoying if you have your debugger set to "break on first chance exceptions", and it also fills up the debug output window with first chance exception messages. 如果您将调试器设置为“在第一次机会异常时中断”,这实际上很烦人,并且它还会使用第一次机会异常消息填满调试输出窗口。 You can suppress these, but visual studio makes it annoying to do so. 你可以抑制这些,但是visual studio让你很烦人。
  • These two things add up - on a cold start your app can take noticeably longer to load. 这两件事情加起来 - 冷启动你的应用程序可能需要更长时间才能加载。 On a core i7 with an SSD, I found that when my WPF app first loaded all the dynamic stuff, it would stall for about 1-2 seconds loading assemblies, JITing, and throw-catching exceptions. 在带有SSD的核心i7上,我发现当我的WPF应用程序首次加载所有动态内容时,它会停止大约1-2秒加载程序集,JITing和抛出异常。 (Interestingly IronRuby doesn't have these problems, it's implementation of the DLR is much better than C#'s) (有趣的是IronRuby没有这些问题,它的DLR实现比C#好得多)
  • Once things are loaded the performance is very good. 加载东西后,性能非常好。
  • Dynamics kill intellisense and other nice features of visual studio. 动态杀死智能感知和视觉工作室的其他不错的功能。 While I personally didn't mind this as I have a background of doing lots of ruby code, several other developers in my organization got annoyed. 虽然我个人并不介意,因为我有很多ruby代码的背景,我组织中的其他几个开发人员都很生气。
  • Dynamics can make debugging a LOT harder. 动力学可以使调试变得更难。 Languages like ruby/javascript provide a REPL (interactive prompt) which helps them, but C# doesn't yet have one. 像ruby / javascript这样的语言提供了一个帮助他们的REPL(交互式提示),但C#还没有。 If you're just using dynamic to resolve methods, it won't be so bad, but if you try and use it to dynamically implement data structures (ExpandoObject, etc) then debugging becomes a real pain in C#. 如果您只是使用动态来解析方法,它就不会那么糟糕,但如果您尝试使用它来动态实现数据结构(ExpandoObject等),那么调试将成为C#的真正痛苦。 My co-workers were even more annoyed at me when they had to debug some code using an ExpandoObject. 当他们不得不使用ExpandoObject调试一些代码时,我的同事对我更加恼火。

On the whole: 总体上:

  • If you can do something without needing dynamics, don't use them. 如果您可以在不需要动态的情况下做某事,请不要使用它们。 The way C# implements them is just too awkward and your co-workers will be angry with you. C#实现它们的方式太尴尬了,你的同事会对你生气。
  • If you only need a very small dynamic feature, use reflection. 如果您只需要一个非常小的动态特征,请使用反射。 The oft-quoted "performance issues" from using reflection are often not a big deal. 使用反射的经常引用的“性能问题”通常不是什么大问题。
  • Especially try and avoid dynamics in a client app due to the load/start performance penalties. 特别是由于负载/启动性能损失,请尝试避免客户端应用程序中的动态。

My advice for this specific situation: 我对这种具体情况的建议:

  • It looks like you can probably avoid dynamics here by just passing things around as Object . 看起来你可以通过传递物体作为Object来避免动态。 I would suggest that you do this. 我建议你这样做。
  • You would have to switch away from using Tuple to pass your pairs of data around, and make some custom class(es), but this would probably also improve your code as then you can attach meaningful names to the data instead of just Item1 and Item2 您将不得不切换使用Tuple来传递您的数据对,并制作一些自定义类,但这可能也会改进您的代码,因为您可以将有意义的名称附加到数据而不仅仅是Item1Item2

I believe that the dynamic keyword was primarily added to make Microsoft Office interop easier, where previously you had to write quite convoluted code (in C#) to be able to use the Microsoft Office API, Office interface code can be much cleaner now. 我相信动态关键字主要是为了使Microsoft Office interop更容易添加,以前你必须编写非常复杂的代码(在C#中)才能使用Microsoft Office API,Office界面代码现在可以更加清晰。

The reson for this is that the Office API was originally written to be used by Visual Basic 6 (or VB script); 对此的共鸣是Office API最初编写为由Visual Basic 6(或VB脚本)使用; .NET 4.0 adds several language features to make this easier (as well as dynamic, also you get named and optional parameters). .NET 4.0添加了几种语言功能,以使其更容易(以及动态,也可以获得命名和可选参数)。

When you use the dynamic keyword, it loses compile-time checking, as the objects using the dynamic keyword are resolved at run-time. 当您使用dynamic关键字时,它会丢失编译时检查,因为使用dynamic关键字的对象在运行时被解析。 There is some memory overhead as the assembly that provides dynamic support has to be loaded in. Also there will be some performance overhead, similar to using Reflection. 由于必须加载提供动态支持的程序集,因此存在一些内存开销。此外,还会有一些性能开销,类似于使用Reflection。

I don't think this is a solution for dynamic. 我不认为这是动态的解决方案。 Dynamic is useful when you need to work with a bunch of different types conditionally. 当您需要有条件地处理一堆不同类型时,动态非常有用。 If this is a string, do something, if it's an int, do something else, if its a Puppy class instance, call bark(). 如果这是一个字符串,做一些事情,如果它是一个int,做其他事情,如果它是一个Puppy类实例,则调用bark()。 dynamic frees you from having to litter code like this with tons of type casting or ugly generics. 动态释放你不必像这样乱丢垃圾代码与大量的类型铸造或丑陋的泛型。 Uses for dynamic and other advanced language features are meant for code generators, interpreters etc... 动态和其他高级语言功能的用途适用于代码生成器,解释器等......

It's a cool feature, but unless your talking to a dynamic language or COM interop, it's only meant for when you have a nasty advanced problem. 这是一个很酷的功能,但除非你使用动态语言或COM互操作,否则它只适用于你有一个讨厌的高级问题。

Instead of using dynamics here, I think you can accomplish the same thing by using by using IList . 我认为你可以通过使用IList来完成同样的事情,而不是在这里使用动态。 (non-generic) Both eliminate compile time type checking, but since generic lists also implement IList , you can still get run-time type checking using IList . (非泛型)两者都消除了编译时类型检查,但由于泛型列表也实现了IList ,您仍然可以使用IList运行时类型检查。

Also, side question, why did you use .Aggregate() instead of .Max() to find the maximum length? 另外,侧面问题,为什么你使用.Aggregate()而不是.Max()来查找最大长度?

I haven't looked at it closely yet but is the use of dynamic keyword really necessary for your use when declaring the collection? 我还没有密切关注它,但在声明集合时,是否真的需要使用动态关键字? In .NET 4.0 there are also new mechanisms to support covariance and contravariance which means you should also be able to use the code below. 在.NET 4.0中,还有支持协方差和逆变的新机制 ,这意味着您还应该能够使用下面的代码。

    var listCollection = new List<IEnumerable<object>>();

    listCollection.Add(new List<int>());

The drawback here is that your list holds read only IEnumerable instances instead of something that could be modified directly if that was something that was required in your implementation. 这里的缺点是你的列表只包含IEnumerable实例,而不是可以直接修改的东西,如果你的实现需要的话。

Other than that consideration I think the use of dynamics are fine as you are using them but you do sacrifice a lot of the safety mechanism that C# normally provides. 除了这个考虑之外,我认为动态的使用很好,因为你正在使用它们,但你确实牺牲了C#通常提供的许多安全机制。 So what I would recommend is that if you use this technique I would recommend writing it within a well contained and tested class that doesn't expose the dynamic type to any larger body of client code. 所以我建议如果你使用这种技术我会建议在一个包含良好且经过测试的类中编写它,它不会将动态类型暴露给任何更大的客户端代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM