值元组与匿名类型性能



我很惊讶还没有这方面的问题。 C# 7 附加值元组。 我试图弄清楚何时应该采用这些功能。

以这个字典为例,使用匿名类型:

var changesTypeMapByEntityState = this.ChangeTracker.Entries()
.Where(x => (int)x.State > (int)EntityState.Unchanged)
.GroupBy(x => new { Type = x.Entity.GetType(), x.State })
.ToDictionary(x => x.Key, x => x.ToList());

VS这个使用值元组的字典

var changesTypeMapByEntityState = this.ChangeTracker.Entries()
.Where(x => (int)x.State > (int)EntityState.Unchanged)
.GroupBy(x => (Type: x.Entity.GetType(), x.State ))
.ToDictionary(x => x.Key, x => x.ToList());

其中哪一个性能更好,使用新语法与旧语法有什么好处?

在这种情况下,没有太大区别。

但在其他情况下,值元组可以具有显著的性能优势。因为它们是值类型而不是引用类型,所以细心的程序员有时可以使用它们来避免在堆上分配也必须管理和收集的新内存。此外,值元组更容易在本地范围之外共享,因此在匿名类型不是的情况下是合法的。

也就是说,值类型和引用类型也可以具有不同的语义,这意味着您可能会遇到对匿名类型的引用更合适的情况,尤其是在复制大量引用的情况下。

最后,GC 内存管理通常不是程序的主要性能驱动因素。价值元组性能优势不太可能产生足够大的差异来急于更改所有旧代码,除非您有无限的时间花在探查器工具上以确保它是成功的。更值得注意的是,哪种选择会产生更清晰的代码或使用更好的语义。

有一个非常明显的情况,你会使用Value Tuple而不是匿名对象,这就是你需要将对象返回给调用方的时候。
有了Value Tuple,你就可以快速返回任意数量的命名属性,这对于匿名对象来说不是很可行。

例如:

public (int Count, string Hello) GetDataTuple()
{
return (1, "world");
}
public object GetDataObject()
{
return new { Count = 1, Hello = "World" };
}

然后:

var dataTuple = GetDataTuple();
Console.WriteLine(dataTuple.Count); // valid
var dataObject = GetDataObject();
Console.WriteLine(dataOjbect.Hello); // invalid

从逻辑上讲,这也适用于类中的属性/字段:

class Test
{
public (int Count, string Hello) DataTuple { get; set; } // valid
public 'A DataObject { get; set; } // obviously invalid
}

我想说的基本优点是元组有名字可以这么说;方法/属性/字段可以类型为元组。

不能有匿名字段或属性(返回匿名类型虽然可行,但并不简单,并且具有令人不舒服的限制)。

另一个重要的区别是匿名类型是引用类型,而值元组是值类型。即使在我们仅寻址隐式类型局部变量的情况下,这也是一个重要的区别,其中用法与您的示例演示的用法基本相同。

我正在做一些研究,我找到了一个很好的基准。 匿名类型预生成了值元组。 但这只是需要运行的众多测试之一,以做出明智的决定。 我只是为了方便而提供这个基准,所以没有其他人需要查找这个。 但这不包括查找或 GC 收集

var valueTupleQuery = from i in Enumerable.Range(0, 100000)
select (a: i, b: i, c: i, d: i, e: i, x: i, y: i + 1, z: i + 2) into x
where x.x > 100001
select (x: x, _: 0) into t
where t.x.x < 0
select t.x;
var anonymousQuery = from i in Enumerable.Range(0, 100000)
select new { a = i, b = i, c = i, d = i, e = i, x = i, y = i + 1, z = i + 2 } into x
where x.x > 100001
select (x: x, _: 0) into t
where t.x.x < 0
select t.x;
var stopwatch = new Stopwatch();
stopwatch.Restart();
for (var i = 0; i < 1000; i++)
{
valueTupleQuery.ToArray();
}
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
for (var i = 0; i < 1000; i++)
{
anonymousQuery.ToArray();
}
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);

运行在联想y700上

值元组采用:3426 MS

匿名类型:3137 MS

注意

如果有人想知道,我决定一起避免使用密钥,只是嵌套我的字典

var changesTypeMapByEntityState = this.ChangeTracker.Entries()
.Where(x => (int)x.State > (int)EntityState.Unchanged)
.GroupBy(x => x.Entity.GetType())
.ToDictionary(x => x.Key, 
x => x.GroupBy(g => g.State)
.ToDictionary(k => k.Key, v => v.ToList()));

最新更新