是否可以在 C# 中创建“完美转发”泛型集合?

Coo*_*ots 2 c# c++ c++11

考虑这个 C++ 代码片段:

std::vector<Foo> bar;
bar.emplace_back(9001);    //Foo defines a constructor that takes int
Run Code Online (Sandbox Code Playgroud)

这将创建一个 type 对象Foo,传递任何构造函数参数,并将新对象存储在向量中。功能上等价于bar.push_back(Foo(9001));,但效率更高,因为没有Foo创建临时文件。

有没有办法在 C# 中实现类似的功能?

如果是这样,它会提供任何真正的好处,就像在 C++ 中一样,还是只是语法糖

编辑:这个问题特别是关于 C#(结构)中的值类型集合;我正在寻找的好处(除了简化的Add语法)类似于 C++11emplace方法的好处- 无需构造、复制和销毁 (GC) 临时对象。

que*_*atl 5

是的,这是可能的,但它......很麻烦。

在 .Net 中,结构总是被复制。任何以任何方式传递结构的尝试都会产生一个副本:

SuperList x;
x.Add(new MyStruct());
Run Code Online (Sandbox Code Playgroud)

更重要的是,这个实际上可能会创建至少 3 个实例:

  • 你创造的
  • 复制传递到 Add 方法
  • 在 Add 方法将写入的列表数据存储中复制

您现在可以使用 factory 删除其中的一些

SuperList x;
x.Add(() => new MyStruct());
Run Code Online (Sandbox Code Playgroud)

但是之后:

  • 你有一个由工厂创建的
  • 并复制到工厂将写入的列表数据存储中

所以......如果你试图以任何方式将新实例传递到存储中,它将被复制。

我能想到的不涉及制作任何副本的唯一方法是让商店为您创建它。最简单的例子是..:

SuperList x;
x.AddNewItems(1);
Run Code Online (Sandbox Code Playgroud)

没关系商店如何实现它。假设它足够聪明,不做任何副本。例如,它可以使用一种 LinkedList<> 并只附加一个新节点。

这样,通过要求商店为您创建一个对象,商店就有机会创建该项目。当然,它必须正确实施,但这是另一个话题。新项目可能会使用所有默认值创建,除非您CreateItem使用更多参数和一些特定逻辑详细说明,但是 .. 假设使用默认值创建它是可以的。

现在怎么办?现在,当然,工作(即阅读、修改等)该项目存在问题:

class SuperList
{
    MyStruct GetItem(int id);
}
Run Code Online (Sandbox Code Playgroud)

好吧,这显然会返回一个副本。哎呀。

如果您使用的是 C# 7.0,您可以使用 ref 变量,也许,我还没有尝试过:

class SuperList
{
    ref MyStruct GetItem(int idx);
}

ref MyStruct aRef = x.GetItem(x.Count-1);
aRef.Name = "mom";
Run Code Online (Sandbox Code Playgroud)

但除非它是 C#7,否则你不能也必须在很长一段时间内解决它:

class SuperList
{
    delegate void ItemAccessor(ref MyStruct y); // <- REF! no copy
    void WorkWithItem(int idx, ItemAccessor func);
}

x.WorkWithItem(1, (ref MyStruct it) => {
    it.Name="mom";
});
Run Code Online (Sandbox Code Playgroud)

最后,由于我们可以创建一个store并且可以以某种方式访问​​它的项目,你可以做任何你想做的事情,把它包装在瓷层中,以提供任何用户前端来创建和初始化这些对象。例如,您可以使用“构造函数”方法创建 API:

var x = new SuperList<T,U,...>( (ref item, T t, U u, ..) => {
    item.First = t;
    item.Second = u;
    ...
});

x.CreateItem(new T(), new U(), ...);
Run Code Online (Sandbox Code Playgroud)

或者通过object[] args和反射来完成。任何。使用或实施或两者兼而有之都会很麻烦,我会多次考虑采用这种方式。class使用起来比 更方便struct。但如果真的需要,所有这些当然都是可能的。

啊,是的,问题的第二部分:

如果是这样,它会提供任何真正的好处,就像在 C++ 中一样,还是只是语法糖?

为什么,当然,根据定义,好处是:不制作副本。这意味着如果对象很大,您将获得性能优势,显而易见,假设复制需要大量时间

另一方面,如果对象很小,由于传递ref指针、额外的顺从、调用方法、创建委托实例等,您将获得性能损失。对于小型结构,结果可能是,您构建的基础设施比简单地来回复制该结构需要更多时间(而不是空间!)来调用。这是您在进行此类优化时应该考虑的事情。

我实际上不知道需要多大的结构才能获得好处。复制结构很快,它们就是为此而设计的。它实际上几乎是原始的逐字节复制,处理器可以非常快地完成

我记得许多关于值类型和引用类型的访问、GC'inc、复制等的性能测试。在互联网上搜索,一定要阅读他们测量的代码,因为有很多错误可能会扭曲结果。无论如何,对于复制structs来说,它完全取决于它们的字节大小和复制内存块的速度。

所以.. 有一个大的结构.. 使我们的 SmartList 值得。在结构体中放什么?海量数据?不,结构会很小,并且会保留对大数组的引用:

struct X { public int[] f; }
static void Main()
{
    X a;a.f = new []{5};
    X b;b = a;
    a.f[0]=4;
    Console.WriteLine("Hello " + b.f[0]); // FOUR!
}
Run Code Online (Sandbox Code Playgroud)

如果 a int[]inside astruct实际上是一个引用,那么您在结构中保留的几乎所有内容通常都是一个占用空间很小的小引用。您需要struct具有大量字段和/或其中大部分字段本身需要大量非数组值类型的.. 这不是您经常看到的东西。正如您在该int[]示例中看到的那样,打破“大值类型”的想法并将结构分解为引用的部分太容易了。