![](/img/trans.png)
[英]Why is ConcurrentBag<T> so slow in .Net (4.0)? Am I doing it wrong?
[英]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<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#还是其他任何地方......),我只想问我是否正确行事。 显然代码按预期工作,但这是正确的方法吗? 我有什么明显的优化或缺陷吗?
我已经对C#中的动态做了很多讨论,我最初认为它们会非常整洁,因为我是Ruby / Javascript完成的动态类型的忠实粉丝,但在实现中却很遗憾。 因此,我对“我是否正确行事”的看法归结为“这个问题非常适合动态” - 这是我对此的看法。
总体上:
我对这种具体情况的建议:
Object
来避免动态。 我建议你这样做。 Tuple
来传递您的数据对,并制作一些自定义类,但这可能也会改进您的代码,因为您可以将有意义的名称附加到数据而不仅仅是Item1
和Item2
我相信动态关键字主要是为了使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.