.NET 4.0 中的动态:我做得对吗?
昨天,我使用 .NET 4.0 中新的 dynamic
类型编写了第一行代码。我发现这很有用的场景如下:
我有一个包含多个值列表的类。这可以是 List
、List
、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);
}
}
}
}
下面是我使用的一组简短的单元测试验证我最终得到了所需的行为:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我还没有仔细研究它,但是在声明集合时使用动态关键字真的有必要吗?在 .NET 4.0 中,还有新机制来支持协变和逆变,意味着您还应该能够使用下面的代码。
这里的缺点是你的列表只保存只读的 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.
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.
我在 C# 中对动态进行了相当多的修改,我最初认为它们会非常简洁,因为我是 Ruby/Javascript 动态类型的忠实粉丝,但遗憾的是对实现感到失望。因此,我对“我做得对吗”的看法归结为“这个问题是否适合动力学”——这是我对此的想法。
总的来说:
我对这种特定情况的建议:
Object
传递来避免动态。我建议你这样做。Tuple
来传递数据对,并创建一些自定义类,但这也可能会改进您的代码,因为您可以将有意义的名称附加到数据而不仅仅是Item1
和Item2
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.
On the whole:
My advice for this specific situation:
Object
. I would suggest that you do this.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 justItem1
andItem2
我相信添加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.
我认为这不是动态的解决方案。当您需要有条件地使用一堆不同类型时,动态非常有用。如果这是一个字符串,则执行某些操作;如果是一个 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.
我认为您可以通过使用
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 implementIList
, you can still get run-time type checking usingIList
.Also, side question, why did you use
.Aggregate()
instead of.Max()
to find the maximum length?