【问题标题】:C# struct with object as data member以对象为数据成员的 C# 结构
【发布时间】:2010-05-02 06:48:34
【问题描述】:

我们知道,在 C# 中,结构是按值传递的,而不是按引用传递的。因此,如果我有一个包含以下数据成员的结构:

private struct MessageBox
{
    // data members
    private DateTime dm_DateTimeStamp; // a struct type
    private TimeSpan dm_TimeSpanInterval; // also a struct
    private ulong dm_MessageID; // System.Int64 type, struct
    private String dm_strMessage; // an object (hence a reference is stored here)
    // more methods, properties, etc ...
}

所以当 MessageBox 作为参数传递时,会在堆栈上生成一个 COPY,对吗? 就数据成员的复制方式而言,这意味着什么? 前两个是结构类型,因此应复制 DateTime 和 TimeSpan。第三种是原语,所以也被复制了。但是 dm_strMessage 呢,它是对对象的引用?当它被复制时,会创建另一个对相同字符串的引用,对吗?对象本身驻留在堆中,并且不会被复制(堆上只有一个实例。)所以现在我们必须引用同一个 String 类型的对象。如果这两个引用是从不同的线程访问的,那么可以想象,String 对象可能会因同时从两个不同的方向修改而损坏。 MSDN 文档说 System.String 是线程安全的。这是否意味着 String 类有一个内置机制来防止对象在此处描述的那种情况下被破坏? 我试图弄清楚我的 MessageBox 结构是否有任何潜在的缺陷/陷阱是结构与类。 感谢您的任何意见。

源.能量。

【问题讨论】:

标签: c# struct thread-safety


【解决方案1】:

字符串不能被多线程访问“破坏”,因为它们是不可变的。

你应该避免让你的结构可变。阅读this question 和答案以获取更多信息。

我试图弄清楚我的 MessageBox 结构是否存在任何潜在的缺陷/陷阱,即结构与类。

它可能不应该是一个结构。有关choosing between a class and a struct,请参阅 MSDN 上的指南。

除非类型具有以下所有特征,否则不要定义结构:

  • 它在逻辑上表示单个值,类似于原始类型(整数、双精度等)。
  • 它的实例大小小于 16 字节。
  • 它是不可变的。
  • 不必经常装箱。

我认为您的 MessageBox 肯定违反了第一条和第二条准则,也可能违反了第三条,具体取决于可用的方法。

【讨论】:

  • 谢谢你们的回复,伙计们。您的回答为我指明了正确的方向,我发现 几个 与结构和不变性相关的帖子,所以现在我认为我对概念的理解要好得多。还有一个值得解决的后续问题:如果在堆栈上创建结构(这是正确的,不是吗?),并且我管理结构的队列(即 System.Collection.Queue),这是否意味着所有队列中的结构存储在堆栈中?这可能不是我想要的,对吧?特别是因为其中一个数据成员是一个字符串,它可能会变长。
  • 总是在堆栈上创建结构是不正确的。你应该尽量不要将你对类和结构的理解建立在它们是存储在堆栈上还是堆上。这是一个实现细节,将来可能会改变(但可能不会)。但是要回答您的问题,对字符串的 reference 可以存储在堆栈或堆中,但字符串中的 character data 将在堆中,即使对于结构中的字符串。
【解决方案2】:

首先,您的第一句话暗示您认为类是通过引用传递的。情况并非如此——引用是按值传递的(默认情况下)。有关详细信息,请参阅my article on parameter passing。明白了这一点,或许其他方面就更清楚了。

你的问题实际上是关于两个不同的事情:

  • 如何复制结构值
  • 在线程之间共享字符串有多安全

我认为将两者分开会有所帮助。

复制结构的值时,无论成员是值类型还是引用类型,都会以相同的方式处理成员。该值被简单地逐位复制。要理解的重要一点是dm_strMessage 的值是一个引用,而不是一个字符串对象。该引用已被复制。

这并不比这段代码更有害:

string message = GetMessageFromSomewhere();
string otherMessage = message;

完全相同的事情发生:message 的值被复制到otherMessage:这两个变量具有相同的值,这是对单个字符串对象的引用。

到目前为止,这与线程无关。现在如果你在多个线程之间共享一个字符串引用,那是安全的——因为字符串是不可变的。您无法更改字符串对象的内容,因此两个字符串可以完美地从同一个对象中读取数据,而没有损坏的风险。 .NET 中的许多其他类型不也是如此。例如,在可能会修改列表的多个线程之间共享List<T> 是不安全的。

【讨论】:

  • 好的,乔恩,谢谢。澄清一下:在您的示例中,变量消息(字符串类型)是对字符串对象的引用,因此当消息作为参数传递时,引用本身被复制,这意味着它是按值传递的,如您所说。表示 String 对象的数据驻留在堆上,绝对不会被复制。所以在消息传递之后,假设它没有超出范围,我们最终会得到对同一个 String 对象的两个引用。以上所有内容都是准确和正确的吗?是的,我现在看到我最初的问题与线程/线程安全无关。
【解决方案3】:

从 GC 的角度来看,传递单个结构与将字段作为单个参数传递没有太大区别。在结构中传递字符串与将字符串作为简单参数传递时,当然没有什么可担心的。

字符串是不可变的,因此无论有多少线程共享它们,它们都不会被破坏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-26
    • 2012-10-28
    • 1970-01-01
    • 2021-12-27
    相关资源
    最近更新 更多