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

发布于 2024-11-06 00:11:37 字数 5603 浏览 0 评论 0原文

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

我有一个包含多个值列表的类。这可以是 ListListList 或任何类型的列表。使用它们的方式是,我向其中一个或多个列表添加一个值。然后我“同步”它们,以便它们最终都具有相同的长度(那些太短的用默认值填充)。然后我继续添加更多值,再次同步等。目标是一个列表中任何索引处的项目与另一个列表中相同索引处的项目相关。 (是的,通过将所有这些包装在另一个类中可能可以更好地解决这个问题,但这不是本例的重点。)

我在几个类中都有这个构造,所以我想让列表的同步像通用的那样可能的。但由于列表的内部类型可能会有所不同,因此这并不像我最初想象的那么简单。但是,输入今天的英雄:动力学:)

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

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# 中还是在其他任何地方......),我只是想问我是否做得正确。显然,代码按预期工作,但是这是正确的方法吗?是否有任何明显的优化或我遗漏的陷阱等?

Yesterday I wrote my first lines of code using the new dynamic type in .NET 4.0. 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. 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<ICollection<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<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. 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

病毒体 2024-11-13 00:11:38

我还没有仔细研究它,但是在声明集合时使用动态关键字真的有必要吗?在 .NET 4.0 中,还有新机制来支持协变和逆变,意味着您还应该能够使用下面的代码。

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

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

这里的缺点是你的列表只保存只读的 IEnumerable 实例,而不是可以直接修改的东西(如果你的实现中需要的话)。

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

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.

    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.

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. 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.

半透明的墙 2024-11-13 00:11:37

我在 C# 中对动态进行了相当多的修改,我最初认为它们会非常简洁,因为我是 Ruby/Javascript 动态类型的忠实粉丝,但遗憾的是对实现感到失望。因此,我对“我做得对吗”的看法归结为“这个问题是否适合动力学”——这是我对此的想法。

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

总的来说:

  • 如果你可以做一些不需要动态的事情,就不要使用它们。 C# 实现它们的方式太尴尬了,你的同事会生你的气。
  • 如果您只需要非常小的动态功能,请使用反射。经常被引用的使用反射的“性能问题”通常不是什么大问题。
  • 尤其要尝试避免由于加载/启动性能损失而导致客户端应用程序出现动态变化。

我对这种特定情况的建议:

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

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. 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.
  • The C-Sharp runtime binder internally throws and catches an exception the first time a method is resolved dynamically. This happens per call-site (i.e. if you have 10 lines of code calling methods on a dynamic object you get 10 exceptions). 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.
  • 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. (Interestingly IronRuby doesn't have these problems, it's implementation of the DLR is much better than C#'s)
  • 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.
  • 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. 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#. My co-workers were even more annoyed at me when they had to debug some code using an 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.
  • 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. 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
离线来电— 2024-11-13 00:11:37

我相信添加dynamic关键字主要是为了使Microsoft Office互操作更容易,以前您必须编写相当复杂的代码(用C#)才能使用Microsoft Office API,现在Office界面代码可以更加简洁。

其原因在于 Office API 最初是为 Visual Basic 6(或 VB 脚本)使用而编写的; .NET 4.0 添加了多种语言功能,使之变得更容易(以及动态功能,您还可以获得命名参数和可选参数)。

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

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.

The reson for this is that the Office API was originally written to be used by Visual Basic 6 (or VB script); .NET 4.0 adds several language features to make this easier (as well as dynamic, also you get named and optional parameters).

When you use the dynamic keyword, it loses compile-time checking, as the objects using the dynamic keyword are resolved at run-time. 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.

萌化 2024-11-13 00:11:37

我认为这不是动态的解决方案。当您需要有条件地使用一堆不同类型时,动态非常有用。如果这是一个字符串,则执行某些操作;如果是一个 int,则执行其他操作;如果是一个 Puppy 类实例,则调用 bark()。动态使您不必使用大量类型转换或丑陋的泛型来乱扔这样的代码。动态和其他高级语言功能的用途是用于代码生成器、解释器等...

这是一个很酷的功能,但除非您与动态语言或 COM 互操作性交谈,否则它仅适用于遇到棘手的高级问题时。

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(). 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.

饭团 2024-11-13 00:11:37

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

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

Instead of using dynamics here, I think you can accomplish the same thing by using by using 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.

Also, side question, why did you use .Aggregate() instead of .Max() to find the maximum length?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文