繁体   English   中英

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

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

昨天我使用.NET 4.0中的新dynamic类型编写了我的第一行代码。 我发现这个有用的场景如下:

我有一个类,里面有几个值列表。 这可以是List<string>List<bool>List<int>或任何类型的列表。 使用它们的方式是我向这些列表中的一个或多个添加值。 然后我“同步”它们,以便它们都以相同的长度结束(那些太短的都填充了默认值)。 然后我继续添加更多值,再次同步等。目标是其中一个列表中任何索引处的项与另一个列表中相同索引处的项相关。 (是的,通过将所有这些包装在另一个类中可能可以更好地解决这个问题,但在这种情况下,这不是重点。)

我有几个类中的这个构造,所以我想让列表的同步尽可能通用。 但由于列表的内部类型可能会有所不同,这并不像我最初想象的那样直截了当。 但是,进入当天的英雄:动态:)

我编写了以下帮助器类,它可以获取列表(任何类型)的集合以及每个列表的默认值:

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);
            }
        }
    }
}

以下是我用来验证我最终得到所需行为的一小部分单元测试:

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]);
        }
    }
}

所以,既然这是我动态的第一次镜头(无论是在C#还是其他任何地方......),我只想问我是否正确行事。 显然代码按预期工作,但这是正确的方法吗? 我有什么明显的优化或缺陷吗?

我已经对C#中的动态做了很多讨论,我最初认为它们会非常整洁,因为我是Ruby / Javascript完成的动态类型的忠实粉丝,但在实现中却很遗憾。 因此,我对“我是否正确行事”的看法归结为“这个问题非常适合动态” - 这是我对此的看法。

  • 拉入的负载性能和JIT所有与动态相关的组件可能非常严重。
  • C-Sharp运行时绑定程序在内部抛出并在第一次动态解析方法时捕获异常。 每个调用站点都会发生这种情况(例如,如果您在动态对象上有10行代码调用方法,则会获得10个异常)。 如果您将调试器设置为“在第一次机会异常时中断”,这实际上很烦人,并且它还会使用第一次机会异常消息填满调试输出窗口。 你可以抑制这些,但是visual studio让你很烦人。
  • 这两件事情加起来 - 冷启动你的应用程序可能需要更长时间才能加载。 在带有SSD的核心i7上,我发现当我的WPF应用程序首次加载所有动态内容时,它会停止大约1-2秒加载程序集,JITing和抛出异常。 (有趣的是IronRuby没有这些问题,它的DLR实现比C#好得多)
  • 加载东西后,性能非常好。
  • 动态杀死智能感知和视觉工作室的其他不错的功能。 虽然我个人并不介意,因为我有很多ruby代码的背景,我组织中的其他几个开发人员都很生气。
  • 动力学可以使调试变得更难。 像ruby / javascript这样的语言提供了一个帮助他们的REPL(交互式提示),但C#还没有。 如果您只是使用动态来解析方法,它就不会那么糟糕,但如果您尝试使用它来动态实现数据结构(ExpandoObject等),那么调试将成为C#的真正痛苦。 当他们不得不使用ExpandoObject调试一些代码时,我的同事对我更加恼火。

总体上:

  • 如果您可以在不需要动态的情况下做某事,请不要使用它们。 C#实现它们的方式太尴尬了,你的同事会对你生气。
  • 如果您只需要一个非常小的动态特征,请使用反射。 使用反射的经常引用的“性能问题”通常不是什么大问题。
  • 特别是由于负载/启动性能损失,请尝试避免客户端应用程序中的动态。

我对这种具体情况的建议:

  • 看起来你可以通过传递物体作为Object来避免动态。 我建议你这样做。
  • 您将不得不切换使用Tuple来传递您的数据对,并制作一些自定义类,但这可能也会改进您的代码,因为您可以将有意义的名称附加到数据而不仅仅是Item1Item2

我相信动态关键字主要是为了使Microsoft Office interop更容易添加,以前你必须编写非常复杂的代码(在C#中)才能使用Microsoft Office API,Office界面代码现在可以更加清晰。

对此的共鸣是Office API最初编写为由Visual Basic 6(或VB脚本)使用; .NET 4.0添加了几种语言功能,以使其更容易(以及动态,也可以获得命名和可选参数)。

当您使用dynamic关键字时,它会丢失编译时检查,因为使用dynamic关键字的对象在运行时被解析。 由于必须加载提供动态支持的程序集,因此存在一些内存开销。此外,还会有一些性能开销,类似于使用Reflection。

我不认为这是动态的解决方案。 当您需要有条件地处理一堆不同类型时,动态非常有用。 如果这是一个字符串,做一些事情,如果它是一个int,做其他事情,如果它是一个Puppy类实例,则调用bark()。 动态释放你不必像这样乱丢垃圾代码与大量的类型铸造或丑陋的泛型。 动态和其他高级语言功能的用途适用于代码生成器,解释器等......

这是一个很酷的功能,但除非你使用动态语言或COM互操作,否则它只适用于你有一个讨厌的高级问题。

我认为你可以通过使用IList来完成同样的事情,而不是在这里使用动态。 (非泛型)两者都消除了编译时类型检查,但由于泛型列表也实现了IList ,您仍然可以使用IList运行时类型检查。

另外,侧面问题,为什么你使用.Aggregate()而不是.Max()来查找最大长度?

我还没有密切关注它,但在声明集合时,是否真的需要使用动态关键字? 在.NET 4.0中,还有支持协方差和逆变的新机制 ,这意味着您还应该能够使用下面的代码。

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

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

这里的缺点是你的列表只包含IEnumerable实例,而不是可以直接修改的东西,如果你的实现需要的话。

除了这个考虑之外,我认为动态的使用很好,因为你正在使用它们,但你确实牺牲了C#通常提供的许多安全机制。 所以我建议如果你使用这种技术我会建议在一个包含良好且经过测试的类中编写它,它不会将动态类型暴露给任何更大的客户端代码。

暂无
暂无

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

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